mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 08:44:28 -05:00
Erased mode in CI (#3640)
* Erased mode in CI * Trigger CI * Rename dev_mode erased_mode plus add to more matrices * nested routes in separate component fix * Fix lint * Small fixes * Fixes * proc-macro rustflags cross-compilation workaround with internal erasure feature for leptos_macro * Re-trigger CI * fix unrelated doc CI and remove unneeded IntoAttribute trait * Fix StaticVec rebuild() fn * Conflict fixes * Maybe fix * Bump example toolchain
This commit is contained in:
1
.github/workflows/ci-changed-examples.yml
vendored
1
.github/workflows/ci-changed-examples.yml
vendored
@@ -28,5 +28,6 @@ jobs:
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
erased_mode: ${{ matrix.erased_mode }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: stable
|
||||
|
||||
1
.github/workflows/ci-examples.yml
vendored
1
.github/workflows/ci-examples.yml
vendored
@@ -25,5 +25,6 @@ jobs:
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
erased_mode: ${{ matrix.erased_mode }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: stable
|
||||
|
||||
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -25,5 +25,6 @@ jobs:
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
erased_mode: ${{ matrix.erased_mode }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: nightly-2025-02-19
|
||||
|
||||
@@ -50,5 +50,5 @@ jobs:
|
||||
echo "matrix={\"directory\":${{ steps.changed-dirs.outputs.all_changed_files }}}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# Create matrix with one item to prevent an empty vector error
|
||||
echo "matrix={\"directory\":[\"NO_CHANGE\"]}" >> "$GITHUB_OUTPUT"
|
||||
echo "matrix={\"directory\":[\"NO_CHANGE\"], \"erased_mode\": [false, true]}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
2
.github/workflows/get-examples-matrix.yml
vendored
2
.github/workflows/get-examples-matrix.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
sed 's/\/$//' |
|
||||
jq -R -s -c 'split("\n")[:-1]')
|
||||
echo "Example Directories: $examples"
|
||||
echo "matrix={\"directory\":$examples}" >> "$GITHUB_OUTPUT"
|
||||
echo "matrix={\"directory\":$examples, \"erased_mode\": [false, true]}" >> "$GITHUB_OUTPUT"
|
||||
- name: Print Location Info
|
||||
run: |
|
||||
echo "Workspace: ${{ github.workspace }}"
|
||||
|
||||
2
.github/workflows/get-leptos-matrix.yml
vendored
2
.github/workflows/get-leptos-matrix.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
sed "s|$(pwd)/||" |
|
||||
jq -R -s -c 'split("\n")[:-1]')
|
||||
echo "Leptos Directories: $crates"
|
||||
echo "matrix={\"directory\":$crates}" >> "$GITHUB_OUTPUT"
|
||||
echo "matrix={\"directory\":$crates, \"erased_mode\": [false, true]}" >> "$GITHUB_OUTPUT"
|
||||
- name: Print Location Info
|
||||
run: |
|
||||
echo "Workspace: ${{ github.workspace }}"
|
||||
|
||||
6
.github/workflows/run-cargo-make-task.yml
vendored
6
.github/workflows/run-cargo-make-task.yml
vendored
@@ -5,6 +5,9 @@ on:
|
||||
directory:
|
||||
required: true
|
||||
type: string
|
||||
erased_mode:
|
||||
required: true
|
||||
type: boolean
|
||||
cargo_make_task:
|
||||
required: true
|
||||
type: string
|
||||
@@ -15,9 +18,10 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
RUSTFLAGS: ${{ inputs.erased_mode && '--cfg erase_components' || '' }}
|
||||
jobs:
|
||||
test:
|
||||
name: Run ${{ inputs.cargo_make_task }} (${{ inputs.toolchain }})
|
||||
name: "Run ${{ inputs.cargo_make_task }} (${{ inputs.toolchain }}) (erased_mode: ${{ inputs.erased_mode }})"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Free Disk Space
|
||||
|
||||
@@ -85,7 +85,7 @@ pub fn fetch_example() -> impl IntoView {
|
||||
.map(|s| {
|
||||
view! {
|
||||
<li>
|
||||
<img src=s.clone()/>
|
||||
<img src=s.clone() />
|
||||
</li>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -149,12 +149,12 @@ pub fn App() -> impl IntoView {
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<Button id="run" text="Create 1,000 rows" on:click=run/>
|
||||
<Button id="runlots" text="Create 10,000 rows" on:click=run_lots/>
|
||||
<Button id="add" text="Append 1,000 rows" on:click=add/>
|
||||
<Button id="update" text="Update every 10th row" on:click=update/>
|
||||
<Button id="clear" text="Clear" on:click=clear/>
|
||||
<Button id="swaprows" text="Swap Rows" on:click=swap_rows/>
|
||||
<Button id="run" text="Create 1,000 rows" on:click=run />
|
||||
<Button id="runlots" text="Create 10,000 rows" on:click=run_lots />
|
||||
<Button id="add" text="Append 1,000 rows" on:click=add />
|
||||
<Button id="update" text="Update every 10th row" on:click=update />
|
||||
<Button id="clear" text="Clear" on:click=clear />
|
||||
<Button id="swaprows" text="Swap Rows" on:click=swap_rows />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,6 @@ use leptos_router::{
|
||||
},
|
||||
hooks::{use_navigate, use_params, use_query_map},
|
||||
params::Params,
|
||||
MatchNestedRoutes,
|
||||
};
|
||||
use leptos_router_macro::path;
|
||||
use std::time::Duration;
|
||||
@@ -33,7 +32,7 @@ pub fn RouterExample() -> impl IntoView {
|
||||
<Router set_is_routing>
|
||||
// shows a progress bar while async data are loading
|
||||
<div class="routing-progress">
|
||||
<RoutingProgress is_routing max_time=Duration::from_millis(250)/>
|
||||
<RoutingProgress is_routing max_time=Duration::from_millis(250) />
|
||||
</div>
|
||||
<nav>
|
||||
// ordinary <a> elements can be used for client-side navigation
|
||||
@@ -53,15 +52,15 @@ pub fn RouterExample() -> impl IntoView {
|
||||
<Routes transition=true fallback=|| "This page could not be found.">
|
||||
// paths can be created using the path!() macro, or provided as types like
|
||||
// StaticSegment("about")
|
||||
<Route path=path!("about") view=About/>
|
||||
<Route path=path!("about") view=About />
|
||||
<ProtectedRoute
|
||||
path=path!("settings")
|
||||
condition=move || Some(logged_in.get())
|
||||
redirect_path=|| "/"
|
||||
view=Settings
|
||||
/>
|
||||
<Route path=path!("redirect-home") view=|| view! { <Redirect path="/"/> }/>
|
||||
<ContactRoutes/>
|
||||
<Route path=path!("redirect-home") view=|| view! { <Redirect path="/" /> } />
|
||||
<ContactRoutes />
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
@@ -71,11 +70,11 @@ pub fn RouterExample() -> impl IntoView {
|
||||
// You can define other routes in their own component.
|
||||
// Routes implement the MatchNestedRoutes
|
||||
#[component(transparent)]
|
||||
pub fn ContactRoutes() -> impl MatchNestedRoutes + Clone {
|
||||
pub fn ContactRoutes() -> impl leptos_router::MatchNestedRoutes + Clone {
|
||||
view! {
|
||||
<ParentRoute path=path!("") view=ContactList>
|
||||
<Route path=path!("/") view=|| "Select a contact."/>
|
||||
<Route path=path!("/:id") view=Contact/>
|
||||
<Route path=path!("/") view=|| "Select a contact." />
|
||||
<Route path=path!("/:id") view=Contact />
|
||||
</ParentRoute>
|
||||
}
|
||||
.into_inner()
|
||||
@@ -122,7 +121,7 @@ pub fn ContactList() -> impl IntoView {
|
||||
<Suspense fallback=move || view! { <p>"Loading contacts..."</p> }>
|
||||
<ul>{contacts}</ul>
|
||||
</Suspense>
|
||||
<Outlet/>
|
||||
<Outlet />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -166,7 +165,7 @@ pub fn Contact() -> impl IntoView {
|
||||
Some(contact) => Either::Right(view! {
|
||||
<section class="card">
|
||||
<h1>{contact.first_name} " " {contact.last_name}</h1>
|
||||
<p>{contact.address_1} <br/> {contact.address_2}</p>
|
||||
<p>{contact.address_1} <br /> {contact.address_2}</p>
|
||||
</section>
|
||||
}),
|
||||
}
|
||||
@@ -224,10 +223,10 @@ pub fn Settings() -> impl IntoView {
|
||||
<Form action="">
|
||||
<fieldset>
|
||||
<legend>"Name"</legend>
|
||||
<input type="text" name="first_name" placeholder="First"/>
|
||||
<input type="text" name="last_name" placeholder="Last"/>
|
||||
<input type="text" name="first_name" placeholder="First" />
|
||||
<input type="text" name="last_name" placeholder="Last" />
|
||||
</fieldset>
|
||||
<input type="submit"/>
|
||||
<input type="submit" />
|
||||
<p>
|
||||
"This uses the " <code>"<Form/>"</code>
|
||||
" component, which enhances forms by using client-side navigation for "
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2024-01-29"
|
||||
channel = "nightly-2025-02-19"
|
||||
|
||||
@@ -4,7 +4,7 @@ use leptos_router::{
|
||||
hooks::use_params,
|
||||
nested_router::Outlet,
|
||||
params::Params,
|
||||
MatchNestedRoutes, ParamSegment, SsrMode, StaticSegment, WildcardSegment,
|
||||
ParamSegment, SsrMode, StaticSegment, WildcardSegment,
|
||||
};
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@@ -203,20 +203,20 @@ pub struct SuspenseCounters {
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn InstrumentedRoutes() -> impl MatchNestedRoutes + Clone {
|
||||
pub fn InstrumentedRoutes() -> impl leptos_router::MatchNestedRoutes + Clone {
|
||||
// TODO should make this mode configurable via feature flag?
|
||||
let ssr = SsrMode::Async;
|
||||
view! {
|
||||
<ParentRoute path=StaticSegment("instrumented") view=InstrumentedRoot ssr>
|
||||
<Route path=StaticSegment("/") view=InstrumentedTop/>
|
||||
<Route path=StaticSegment("/") view=InstrumentedTop />
|
||||
<ParentRoute path=StaticSegment("item") view=ItemRoot>
|
||||
<Route path=StaticSegment("/") view=ItemListing/>
|
||||
<Route path=StaticSegment("/") view=ItemListing />
|
||||
<ParentRoute path=ParamSegment("id") view=ItemTop>
|
||||
<Route path=StaticSegment("/") view=ItemOverview/>
|
||||
<Route path=WildcardSegment("path") view=ItemInspect/>
|
||||
<Route path=StaticSegment("/") view=ItemOverview />
|
||||
<Route path=WildcardSegment("path") view=ItemInspect />
|
||||
</ParentRoute>
|
||||
</ParentRoute>
|
||||
<Route path=StaticSegment("counters") view=ShowCounters/>
|
||||
<Route path=StaticSegment("counters") view=ShowCounters />
|
||||
</ParentRoute>
|
||||
}
|
||||
.into_inner()
|
||||
@@ -279,32 +279,41 @@ fn InstrumentedRoot() -> impl IntoView {
|
||||
<section id="instrumented">
|
||||
<nav>
|
||||
<a href="/">"Site Root"</a>
|
||||
<A href="./" exact=true>"Instrumented Root"</A>
|
||||
<A href="item/" strict_trailing_slash=true>"Item Listing"</A>
|
||||
<A href="counters" strict_trailing_slash=true>"Counters"</A>
|
||||
<A href="./" exact=true>
|
||||
"Instrumented Root"
|
||||
</A>
|
||||
<A href="item/" strict_trailing_slash=true>
|
||||
"Item Listing"
|
||||
</A>
|
||||
<A href="counters" strict_trailing_slash=true>
|
||||
"Counters"
|
||||
</A>
|
||||
</nav>
|
||||
<FieldNavPortlet/>
|
||||
<Outlet/>
|
||||
<Suspense>{
|
||||
move || Suspend::new(async move {
|
||||
<FieldNavPortlet />
|
||||
<Outlet />
|
||||
<Suspense>
|
||||
{move || Suspend::new(async move {
|
||||
let clear_suspense_counters = move |_| {
|
||||
counters.update(|c| *c = SuspenseCounters::default());
|
||||
};
|
||||
csr_ticket.get().map(|ticket| {
|
||||
let ticket = ticket.0;
|
||||
view! {
|
||||
<ActionForm action=reset_counters>
|
||||
<input type="hidden" name="ticket" value=format!("{ticket}") />
|
||||
<input
|
||||
id="reset-csr-counters"
|
||||
type="submit"
|
||||
value="Reset CSR Counters"
|
||||
on:click=clear_suspense_counters/>
|
||||
</ActionForm>
|
||||
}
|
||||
})
|
||||
})
|
||||
}</Suspense>
|
||||
csr_ticket
|
||||
.get()
|
||||
.map(|ticket| {
|
||||
let ticket = ticket.0;
|
||||
view! {
|
||||
<ActionForm action=reset_counters>
|
||||
<input type="hidden" name="ticket" value=format!("{ticket}") />
|
||||
<input
|
||||
id="reset-csr-counters"
|
||||
type="submit"
|
||||
value="Reset CSR Counters"
|
||||
on:click=clear_suspense_counters
|
||||
/>
|
||||
</ActionForm>
|
||||
}
|
||||
})
|
||||
})}
|
||||
</Suspense>
|
||||
<footer>
|
||||
<nav>
|
||||
<A href="item/3/">"Target 3##"</A>
|
||||
@@ -323,11 +332,17 @@ fn InstrumentedRoot() -> impl IntoView {
|
||||
fn InstrumentedTop() -> impl IntoView {
|
||||
view! {
|
||||
<h1>"Instrumented Tests"</h1>
|
||||
<p>"These tests validates the number of invocations of server functions and suspenses per access."</p>
|
||||
<p>
|
||||
"These tests validates the number of invocations of server functions and suspenses per access."
|
||||
</p>
|
||||
<ul>
|
||||
// not using `A` because currently some bugs with artix
|
||||
<li><a href="item/">"Item Listing"</a></li>
|
||||
<li><a href="item/4/path1/">"Target 41#"</a></li>
|
||||
<li>
|
||||
<a href="item/">"Item Listing"</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="item/4/path1/">"Target 41#"</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
@@ -342,7 +357,7 @@ fn ItemRoot() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<h2>"<ItemRoot/>"</h2>
|
||||
<Outlet/>
|
||||
<Outlet />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +375,9 @@ fn ItemListing() -> impl IntoView {
|
||||
// adding an extra `/` in artix; manually construct `a` instead.
|
||||
// <li><A href=format!("./{item}/")>"Item "{item}</A></li>
|
||||
view! {
|
||||
<li><a href=format!("/instrumented/item/{item}/")>"Item "{item}</a></li>
|
||||
<li>
|
||||
<a href=format!("/instrumented/item/{item}/")>"Item "{item}</a>
|
||||
</li>
|
||||
}
|
||||
)
|
||||
.collect_view()
|
||||
@@ -373,9 +390,7 @@ fn ItemListing() -> impl IntoView {
|
||||
view! {
|
||||
<h3>"<ItemListing/>"</h3>
|
||||
<ul>
|
||||
<Suspense>
|
||||
{item_listing}
|
||||
</Suspense>
|
||||
<Suspense>{item_listing}</Suspense>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
@@ -402,7 +417,7 @@ fn ItemTop() -> impl IntoView {
|
||||
));
|
||||
view! {
|
||||
<h4>"<ItemTop/>"</h4>
|
||||
<Outlet/>
|
||||
<Outlet />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,24 +427,29 @@ fn ItemOverview() -> impl IntoView {
|
||||
let resource = expect_context::<Resource<Option<GetItemResult>>>();
|
||||
let item_view = move || {
|
||||
Suspend::new(async move {
|
||||
let result = resource.await.map(|GetItemResult(item, names)| view! {
|
||||
<p>{format!("Viewing {item:?}")}</p>
|
||||
<ul>{
|
||||
names.into_iter()
|
||||
.map(|name| {
|
||||
// FIXME seems like relative link isn't working, it is currently
|
||||
// adding an extra `/` in artix; manually construct `a` instead.
|
||||
// <li><A href=format!("./{name}/")>{format!("Inspect {name}")}</A></li>
|
||||
let id = item.id;
|
||||
view! {
|
||||
<li><a href=format!("/instrumented/item/{id}/{name}/")>
|
||||
"Inspect "{name.clone()}
|
||||
</a></li>
|
||||
}
|
||||
})
|
||||
.collect_view()
|
||||
}</ul>
|
||||
});
|
||||
let result = resource.await.map(|GetItemResult(item, names)| {
|
||||
view! {
|
||||
<p>{format!("Viewing {item:?}")}</p>
|
||||
<ul>
|
||||
{names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let id = item.id;
|
||||
// FIXME seems like relative link isn't working, it is currently
|
||||
// adding an extra `/` in artix; manually construct `a` instead.
|
||||
// <li><A href=format!("./{name}/")>{format!("Inspect {name}")}</A></li>
|
||||
view! {
|
||||
<li>
|
||||
<a href=format!(
|
||||
"/instrumented/item/{id}/{name}/",
|
||||
)>"Inspect "{name.clone()}</a>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
.collect_view()}
|
||||
</ul>
|
||||
}
|
||||
});
|
||||
suspense_counters.update_untracked(|c| c.item_overview += 1);
|
||||
result
|
||||
})
|
||||
@@ -437,9 +457,7 @@ fn ItemOverview() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<h5>"<ItemOverview/>"</h5>
|
||||
<Suspense>
|
||||
{item_view}
|
||||
</Suspense>
|
||||
<Suspense>{item_view}</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,23 +514,26 @@ fn ItemInspect() -> impl IntoView {
|
||||
));
|
||||
view! {
|
||||
<p>{format!("Inspecting {item:?}")}</p>
|
||||
<ul>{
|
||||
fields.iter()
|
||||
<ul>
|
||||
{fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
// FIXME seems like relative link to root for a wildcard isn't
|
||||
// working as expected, so manually construct `a` instead.
|
||||
// let text = format!("Inspect {name}/{field}");
|
||||
// view! {
|
||||
// <li><A href=format!("{field}")>{text}</A></li>
|
||||
// <li><A href=format!("{field}")>{text}</A></li>
|
||||
// }
|
||||
view! {
|
||||
<li><a href=format!("/instrumented/item/{id}/{name}/{field}")>{
|
||||
format!("Inspect {name}/{field}")
|
||||
}</a></li>
|
||||
<li>
|
||||
<a href=format!(
|
||||
"/instrumented/item/{id}/{name}/{field}",
|
||||
)>{format!("Inspect {name}/{field}")}</a>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
.collect_view()
|
||||
}</ul>
|
||||
.collect_view()}
|
||||
</ul>
|
||||
}
|
||||
});
|
||||
suspense_counters.update_untracked(|c| c.item_inspect += 1);
|
||||
@@ -527,9 +548,7 @@ fn ItemInspect() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<h5>"<ItemInspect/>"</h5>
|
||||
<Suspense>
|
||||
{inspect_view}
|
||||
</Suspense>
|
||||
<Suspense>{inspect_view}</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,7 +609,8 @@ fn ShowCounters() -> impl IntoView {
|
||||
id="reset-counters"
|
||||
type="submit"
|
||||
value="Reset Counters"
|
||||
on:click=clear_suspense_counters/>
|
||||
on:click=clear_suspense_counters
|
||||
/>
|
||||
</ActionForm>
|
||||
}
|
||||
})
|
||||
@@ -601,20 +621,23 @@ fn ShowCounters() -> impl IntoView {
|
||||
<h2>"Counters"</h2>
|
||||
|
||||
<h3 id="suspend-calls">"Suspend Calls"</h3>
|
||||
{move || suspense_counters.with(|c| view! {
|
||||
<dl>
|
||||
<dt>"item_listing"</dt>
|
||||
<dd id="item_listing">{c.item_listing}</dd>
|
||||
<dt>"item_overview"</dt>
|
||||
<dd id="item_overview">{c.item_overview}</dd>
|
||||
<dt>"item_inspect"</dt>
|
||||
<dd id="item_inspect">{c.item_inspect}</dd>
|
||||
</dl>
|
||||
})}
|
||||
{move || {
|
||||
suspense_counters
|
||||
.with(|c| {
|
||||
view! {
|
||||
<dl>
|
||||
<dt>"item_listing"</dt>
|
||||
<dd id="item_listing">{c.item_listing}</dd>
|
||||
<dt>"item_overview"</dt>
|
||||
<dd id="item_overview">{c.item_overview}</dd>
|
||||
<dt>"item_inspect"</dt>
|
||||
<dd id="item_inspect">{c.item_inspect}</dd>
|
||||
</dl>
|
||||
}
|
||||
})
|
||||
}}
|
||||
|
||||
<Suspense>
|
||||
{counter_view}
|
||||
</Suspense>
|
||||
<Suspense>{counter_view}</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,17 +665,17 @@ pub fn FieldNavPortlet() -> impl IntoView {
|
||||
view! {
|
||||
<div id="FieldNavPortlet">
|
||||
<span>"FieldNavPortlet:"</span>
|
||||
<nav>{
|
||||
ctx.0.map(|ctx| {
|
||||
ctx.into_iter()
|
||||
.map(|FieldNavItem { href, text }| {
|
||||
view! {
|
||||
<A href=href>{text}</A>
|
||||
}
|
||||
})
|
||||
.collect_view()
|
||||
})
|
||||
}</nav>
|
||||
<nav>
|
||||
{ctx
|
||||
.0
|
||||
.map(|ctx| {
|
||||
ctx.into_iter()
|
||||
.map(|FieldNavItem { href, text }| {
|
||||
view! { <A href=href>{text}</A> }
|
||||
})
|
||||
.collect_view()
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -100,6 +100,15 @@ trace-component-props = [
|
||||
]
|
||||
delegation = ["tachys/delegation"]
|
||||
|
||||
# Having an erasure feature rather than normal --cfg erase_components for the proc macro crate is a workaround for this rust issue:
|
||||
# https://github.com/rust-lang/cargo/issues/4423
|
||||
# TLDR proc macros will ignore RUSTFLAGS when --target is specified on the cargo command.
|
||||
# This works around the issue by the non proc-macro crate which does see RUSTFLAGS enabling the replacement feature on the proc-macro crate, which wouldn't.
|
||||
# This is automatic as long as the leptos crate is depended upon,
|
||||
# downstream usage should never manually enable this feature.
|
||||
[target.'cfg(erase_components)'.dependencies]
|
||||
leptos_macro = { workspace = true, features = ["__internal_erase_components"] }
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = [
|
||||
"nightly",
|
||||
|
||||
@@ -51,6 +51,13 @@ trace-component-props = []
|
||||
actix = ["server_fn_macro/actix"]
|
||||
axum = ["server_fn_macro/axum"]
|
||||
generic = ["server_fn_macro/generic"]
|
||||
# Having an erasure feature rather than normal --cfg erase_components for the proc macro crate is a workaround for this rust issue:
|
||||
# https://github.com/rust-lang/cargo/issues/4423
|
||||
# TLDR proc macros will ignore RUSTFLAGS when --target is specified on the cargo command.
|
||||
# This works around the issue by the non proc-macro crate which does see RUSTFLAGS enabling the replacement feature on the proc-macro crate, which wouldn't.
|
||||
# This is automatic as long as the leptos crate is depended upon,
|
||||
# downstream usage should never manually enable this feature.
|
||||
__internal_erase_components = []
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["nightly", "tracing", "trace-component-props"]
|
||||
@@ -83,9 +90,3 @@ skip_feature_sets = [
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
'cfg(leptos_debuginfo)',
|
||||
'cfg(erase_components)',
|
||||
] }
|
||||
|
||||
@@ -32,6 +32,8 @@ pub struct Model {
|
||||
impl Parse for Model {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mut item = ItemFn::parse(input)?;
|
||||
maybe_modify_return_type(&mut item.sig.output);
|
||||
|
||||
convert_impl_trait_to_generic(&mut item.sig);
|
||||
|
||||
let docs = Docs::new(&item.attrs);
|
||||
@@ -76,6 +78,39 @@ impl Parse for Model {
|
||||
}
|
||||
}
|
||||
|
||||
/// Exists to fix nested routes defined in a separate component in erased mode,
|
||||
/// by replacing the return type with AnyNestedRoute, which is what it'll be, but is required as the return type for compiler inference.
|
||||
fn maybe_modify_return_type(ret: &mut ReturnType) {
|
||||
#[cfg(feature = "__internal_erase_components")]
|
||||
{
|
||||
if let ReturnType::Type(_, ty) = ret {
|
||||
if let Type::ImplTrait(TypeImplTrait { bounds, .. }) = ty.as_ref() {
|
||||
// If one of the bounds is MatchNestedRoutes, we need to replace the return type with AnyNestedRoute:
|
||||
if bounds.iter().any(|bound| {
|
||||
if let syn::TypeParamBound::Trait(trait_bound) = bound {
|
||||
if trait_bound.path.segments.iter().any(
|
||||
|path_segment| {
|
||||
path_segment.ident == "MatchNestedRoutes"
|
||||
},
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}) {
|
||||
*ty = parse_quote!(
|
||||
::leptos_router::any_nested_route::AnyNestedRoute
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "__internal_erase_components"))]
|
||||
{
|
||||
let _ = ret;
|
||||
}
|
||||
}
|
||||
|
||||
// implemented manually because Vec::drain_filter is nightly only
|
||||
// follows std recommended parallel
|
||||
pub fn drain_filter<T>(
|
||||
@@ -296,9 +331,9 @@ impl ToTokens for Model {
|
||||
|
||||
let component = if *is_transparent {
|
||||
body_expr
|
||||
} else if cfg!(erase_components) {
|
||||
} else if cfg!(feature = "__internal_erase_components") {
|
||||
quote! {
|
||||
::leptos::prelude::IntoAny::into_any(
|
||||
::leptos::prelude::IntoMaybeErased::into_maybe_erased(
|
||||
::leptos::reactive::graph::untrack_with_diagnostics(
|
||||
move || {
|
||||
#tracing_guard_expr
|
||||
@@ -613,7 +648,8 @@ impl Parse for DummyModel {
|
||||
drain_filter(&mut attrs, |attr| !attr.path().is_ident("doc"));
|
||||
|
||||
let vis: Visibility = input.parse()?;
|
||||
let sig: Signature = input.parse()?;
|
||||
let mut sig: Signature = input.parse()?;
|
||||
maybe_modify_return_type(&mut sig.output);
|
||||
|
||||
// The body is left untouched, so it will not cause an error
|
||||
// even if the syntax is invalid.
|
||||
|
||||
@@ -281,7 +281,11 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
||||
#[proc_macro]
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
pub fn template(tokens: TokenStream) -> TokenStream {
|
||||
view_macro_impl(tokens, true)
|
||||
if cfg!(feature = "__internal_erase_components") {
|
||||
view(tokens)
|
||||
} else {
|
||||
view_macro_impl(tokens, true)
|
||||
}
|
||||
}
|
||||
|
||||
fn view_macro_impl(tokens: TokenStream, template: bool) -> TokenStream {
|
||||
|
||||
@@ -170,13 +170,13 @@ pub(crate) fn component_to_tokens(
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let spreads = (!(spreads.is_empty())).then(|| {
|
||||
if cfg!(erase_components) {
|
||||
if cfg!(feature = "__internal_erase_components") {
|
||||
quote! {
|
||||
.add_any_attr(vec![#(#spreads.into_attr().into_any_attr(),)*])
|
||||
.add_any_attr(vec![#(#spreads.into_any_attr(),)*])
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
.add_any_attr((#(#spreads,)*).into_attr())
|
||||
.add_any_attr((#(#spreads,)*))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -428,7 +428,7 @@ fn element_children_to_tokens(
|
||||
{ #child }
|
||||
)
|
||||
})
|
||||
} else if cfg!(erase_components) {
|
||||
} else if cfg!(feature = "__internal_erase_components") {
|
||||
Some(quote! {
|
||||
.child(
|
||||
leptos::tachys::view::iterators::StaticVec::from(vec![#(#children.into_maybe_erased()),*])
|
||||
@@ -479,7 +479,7 @@ fn fragment_to_tokens(
|
||||
None
|
||||
} else if children.len() == 1 {
|
||||
children.into_iter().next()
|
||||
} else if cfg!(erase_components) {
|
||||
} else if cfg!(feature = "__internal_erase_components") {
|
||||
Some(quote! {
|
||||
leptos::tachys::view::iterators::StaticVec::from(vec![#(#children.into_maybe_erased()),*])
|
||||
})
|
||||
@@ -768,9 +768,9 @@ pub(crate) fn element_to_tokens(
|
||||
}
|
||||
}
|
||||
|
||||
if cfg!(erase_components) {
|
||||
if cfg!(feature = "__internal_erase_components") {
|
||||
Some(quote! {
|
||||
vec![#(#attributes.into_attr().into_any_attr(),)*]
|
||||
vec![#(#attributes.into_any_attr(),)*]
|
||||
#(.add_any_attr(#additions))*
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -83,7 +83,7 @@ pub(crate) fn slot_to_tokens(
|
||||
let value = attr.value().map(|v| {
|
||||
quote! { #v }
|
||||
})?;
|
||||
Some(quote! { (#name, ::leptos::IntoAttribute::into_attribute(#value)) })
|
||||
Some(quote! { (#name, #value) })
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#[cfg(not(erase_components))]
|
||||
#[cfg(not(feature = "__internal_erase_components"))]
|
||||
#[test]
|
||||
fn ui() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
@@ -78,25 +78,6 @@ pub trait Attribute: NextAttribute + Send {
|
||||
fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send;
|
||||
}
|
||||
|
||||
/// A type that can be converted into an attribute.
|
||||
///
|
||||
/// Used type-erasing attrs and tuples of attrs to [`Vec<AnyAttribute>`] as early as possible to prevent type explosion.
|
||||
pub trait IntoAttribute {
|
||||
/// The type of the attribute.
|
||||
type Output: Attribute;
|
||||
|
||||
/// Converts this into an attribute.
|
||||
fn into_attr(self) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<T: Attribute> IntoAttribute for T {
|
||||
type Output = T;
|
||||
|
||||
fn into_attr(self) -> Self::Output {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds another attribute to this one, returning a new attribute.
|
||||
///
|
||||
/// This is typically achieved by creating or extending a tuple of attributes.
|
||||
@@ -288,7 +269,6 @@ where
|
||||
|
||||
macro_rules! impl_attr_for_tuples {
|
||||
($first:ident, $($ty:ident),* $(,)?) => {
|
||||
#[cfg(not(erase_components))]
|
||||
impl<$first, $($ty),*> Attribute for ($first, $($ty,)*)
|
||||
where
|
||||
$first: Attribute,
|
||||
@@ -376,7 +356,6 @@ macro_rules! impl_attr_for_tuples {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(erase_components))]
|
||||
impl<$first, $($ty),*> NextAttribute for ($first, $($ty,)*)
|
||||
where
|
||||
$first: Attribute,
|
||||
@@ -394,38 +373,15 @@ macro_rules! impl_attr_for_tuples {
|
||||
($first, $($ty,)* new_attr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(erase_components)]
|
||||
impl<$first, $($ty),*> IntoAttribute for ($first, $($ty,)*)
|
||||
where
|
||||
$first: IntoAttribute,
|
||||
$($ty: IntoAttribute),*,
|
||||
{
|
||||
type Output = Vec<$crate::html::attribute::any_attribute::AnyAttribute>;
|
||||
|
||||
fn into_attr(self) -> Self::Output {
|
||||
use crate::html::attribute::any_attribute::IntoAnyAttribute;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let ($first, $($ty,)*) = self;
|
||||
vec![
|
||||
$first.into_attr().into_any_attr(),
|
||||
$($ty.into_attr().into_any_attr(),)*
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_attr_for_tuples_truncate_additional {
|
||||
($first:ident, $($ty:ident),* $(,)?) => {
|
||||
#[cfg(not(erase_components))]
|
||||
impl<$first, $($ty),*> Attribute for ($first, $($ty,)*)
|
||||
where
|
||||
$first: Attribute,
|
||||
$($ty: Attribute),*,
|
||||
|
||||
{
|
||||
const MIN_LENGTH: usize = $first::MIN_LENGTH $(+ $ty::MIN_LENGTH)*;
|
||||
|
||||
@@ -509,7 +465,6 @@ macro_rules! impl_attr_for_tuples_truncate_additional {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(erase_components))]
|
||||
impl<$first, $($ty),*> NextAttribute for ($first, $($ty,)*)
|
||||
where
|
||||
$first: Attribute,
|
||||
@@ -526,38 +481,9 @@ macro_rules! impl_attr_for_tuples_truncate_additional {
|
||||
//($first, $($ty,)*)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(erase_components)]
|
||||
impl<$first, $($ty),*> IntoAttribute for ($first, $($ty,)*)
|
||||
where
|
||||
$first: IntoAttribute,
|
||||
$($ty: IntoAttribute),*,
|
||||
{
|
||||
type Output = $crate::html::attribute::any_attribute::AnyAttribute;
|
||||
|
||||
fn into_attr(self) -> Self::Output {
|
||||
todo!("adding more than 26 attributes is not supported");
|
||||
//crate::html::attribute::any_attribute::IntoAnyAttribute::into_any_attr(self)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(erase_components)]
|
||||
impl<A> IntoAttribute for (A,)
|
||||
where
|
||||
A: IntoAttribute,
|
||||
{
|
||||
type Output = Vec<crate::html::attribute::any_attribute::AnyAttribute>;
|
||||
|
||||
fn into_attr(self) -> Self::Output {
|
||||
use crate::html::attribute::any_attribute::IntoAnyAttribute;
|
||||
|
||||
vec![self.0.into_attr().into_any_attr()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(erase_components))]
|
||||
impl<A> Attribute for (A,)
|
||||
where
|
||||
A: Attribute,
|
||||
@@ -615,7 +541,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(erase_components))]
|
||||
impl<A> NextAttribute for (A,)
|
||||
where
|
||||
A: Attribute,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#[cfg(any(debug_assertions, leptos_debuginfo))]
|
||||
use crate::hydration::set_currently_hydrating;
|
||||
#[cfg(erase_components)]
|
||||
use crate::view::any_view::AnyView;
|
||||
use crate::{
|
||||
html::attribute::Attribute,
|
||||
hydration::{failed_to_cast_element, Cursor},
|
||||
prelude::*,
|
||||
renderer::{CastFrom, Rndr},
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
@@ -133,36 +134,101 @@ trait NextChildren {
|
||||
}
|
||||
|
||||
#[cfg(erase_components)]
|
||||
impl NextChildren for () {
|
||||
fn next_children(
|
||||
self,
|
||||
child: AnyView,
|
||||
) -> crate::view::iterators::StaticVec<AnyView> {
|
||||
vec![child].into()
|
||||
}
|
||||
}
|
||||
mod erased_tuples {
|
||||
use super::*;
|
||||
use crate::view::{any_view::IntoAny, iterators::StaticVec};
|
||||
|
||||
#[cfg(erase_components)]
|
||||
impl<T: RenderHtml> NextChildren for (T,) {
|
||||
fn next_children(
|
||||
self,
|
||||
child: AnyView,
|
||||
) -> crate::view::iterators::StaticVec<AnyView> {
|
||||
use crate::view::any_view::IntoAny;
|
||||
|
||||
vec![self.0.into_owned().into_any(), child].into()
|
||||
impl NextChildren for StaticVec<AnyView> {
|
||||
fn next_children(mut self, child: AnyView) -> StaticVec<AnyView> {
|
||||
self.0.push(child);
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(erase_components)]
|
||||
impl NextChildren for crate::view::iterators::StaticVec<AnyView> {
|
||||
fn next_children(
|
||||
mut self,
|
||||
child: AnyView,
|
||||
) -> crate::view::iterators::StaticVec<AnyView> {
|
||||
self.0.push(child);
|
||||
self
|
||||
impl NextChildren for () {
|
||||
fn next_children(self, child: AnyView) -> StaticVec<AnyView> {
|
||||
vec![child].into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RenderHtml> NextChildren for (T,) {
|
||||
fn next_children(self, child: AnyView) -> StaticVec<AnyView> {
|
||||
vec![self.0.into_owned().into_any(), child].into()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_next_children_tuples {
|
||||
($($ty:ident),*) => {
|
||||
impl<$($ty: RenderHtml),*> NextChildren for ($($ty,)*)
|
||||
{
|
||||
fn next_children(
|
||||
self, child: AnyView,
|
||||
) -> StaticVec<AnyView> {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($ty,)*) = self;
|
||||
vec![$($ty.into_owned().into_any(),)* child].into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_next_children_tuples!(AA, BB);
|
||||
impl_next_children_tuples!(AA, BB, CC);
|
||||
impl_next_children_tuples!(AA, BB, CC, DD);
|
||||
impl_next_children_tuples!(AA, BB, CC, DD, EE);
|
||||
impl_next_children_tuples!(AA, BB, CC, DD, EE, FF);
|
||||
impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG);
|
||||
impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH);
|
||||
impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II);
|
||||
impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II, JJ);
|
||||
impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK);
|
||||
impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
|
||||
SS
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
|
||||
SS, TT
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
|
||||
SS, TT, UU
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
|
||||
SS, TT, UU, VV
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
|
||||
SS, TT, UU, VV, WW
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
|
||||
SS, TT, UU, VV, WW, XX
|
||||
);
|
||||
impl_next_children_tuples!(
|
||||
AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
|
||||
SS, TT, UU, VV, WW, XX, YY
|
||||
);
|
||||
}
|
||||
|
||||
impl<E, At, Ch> AddAnyAttr for HtmlElement<E, At, Ch>
|
||||
@@ -510,10 +576,8 @@ where
|
||||
/// Renders an [`Attribute`] (which can be one or more HTML attributes) into an HTML buffer.
|
||||
pub fn attributes_to_html<At>(attr: At, buf: &mut String) -> String
|
||||
where
|
||||
At: IntoAttribute,
|
||||
At: Attribute,
|
||||
{
|
||||
let attr = attr.into_attr();
|
||||
|
||||
// `class` and `style` are created first, and pushed later
|
||||
// this is because they can be filled by a mixture of values that include
|
||||
// either the whole value (`class="..."` or `style="..."`) and individual
|
||||
|
||||
@@ -22,7 +22,7 @@ pub mod prelude {
|
||||
OnAttribute, OnTargetAttribute, PropAttribute,
|
||||
StyleAttribute,
|
||||
},
|
||||
IntoAttribute, IntoAttributeValue,
|
||||
IntoAttributeValue,
|
||||
},
|
||||
directive::DirectiveAttribute,
|
||||
element::{ElementChild, ElementExt, InnerHtmlAttribute},
|
||||
|
||||
@@ -145,14 +145,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(erase_components)]
|
||||
use crate::html::attribute::any_attribute::{AnyAttribute, IntoAnyAttribute};
|
||||
|
||||
#[cfg(erase_components)]
|
||||
impl<A, B> NextAttribute for Either<A, B>
|
||||
where
|
||||
B: IntoAnyAttribute,
|
||||
A: IntoAnyAttribute,
|
||||
B: crate::html::attribute::any_attribute::IntoAnyAttribute,
|
||||
A: crate::html::attribute::any_attribute::IntoAnyAttribute,
|
||||
{
|
||||
type Output<NewAttr: Attribute> = Vec<AnyAttribute>;
|
||||
|
||||
@@ -160,6 +157,8 @@ where
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
use crate::html::attribute::any_attribute::IntoAnyAttribute;
|
||||
|
||||
vec![
|
||||
match self {
|
||||
Either::Left(left) => left.into_any_attr(),
|
||||
|
||||
@@ -434,6 +434,7 @@ where
|
||||
T: Mountable,
|
||||
{
|
||||
states: Vec<T>,
|
||||
parent: Option<crate::renderer::types::Element>,
|
||||
}
|
||||
|
||||
impl<T> Mountable for StaticVecState<T>
|
||||
@@ -452,6 +453,7 @@ where
|
||||
for state in self.states.iter_mut() {
|
||||
state.mount(parent, marker);
|
||||
}
|
||||
self.parent = Some(parent.clone());
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
|
||||
@@ -479,17 +481,25 @@ where
|
||||
fn build(self) -> Self::State {
|
||||
Self::State {
|
||||
states: self.0.into_iter().map(T::build).collect(),
|
||||
parent: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let Self::State { states } = state;
|
||||
let old = states;
|
||||
// this is an unkeyed diff
|
||||
self.0
|
||||
.into_iter()
|
||||
.zip(old.iter_mut())
|
||||
.for_each(|(new, old)| T::rebuild(new, old));
|
||||
let Self::State { states, .. } = state;
|
||||
|
||||
// StaticVec's in general shouldn't need to be reused, but rebuild() will still trigger e.g. if 2 routes have the same tree,
|
||||
// this can cause problems if differing in lengths. Because we don't use marker nodes in StaticVec, we rebuild the entire vec remounting to the parent.
|
||||
|
||||
for state in states {
|
||||
state.unmount();
|
||||
}
|
||||
let parent = state
|
||||
.parent
|
||||
.take()
|
||||
.expect("parent should always be Some() on a StaticVec rebuild()");
|
||||
*state = self.build();
|
||||
state.mount(&parent, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,7 +603,8 @@ where
|
||||
.into_iter()
|
||||
.map(|child| child.hydrate::<FROM_SERVER>(cursor, position))
|
||||
.collect();
|
||||
Self::State { states }
|
||||
let parent = cursor.current().parent_element();
|
||||
Self::State { states, parent }
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
|
||||
Reference in New Issue
Block a user