mirror of
https://github.com/leptos-rs/leptos
synced 2024-09-20 06:21:57 +00:00
perf: further reduce WASM binary size by ~5-7% (#459)
* Update `leptos_router` docs * Further reducing WASM bundle sizes
This commit is contained in:
parent
c4e693e01e
commit
6d0d70cd17
10 changed files with 349 additions and 299 deletions
|
@ -154,167 +154,182 @@ where
|
|||
instrument(level = "trace", name = "<DynChild />", skip_all)
|
||||
)]
|
||||
fn into_view(self, cx: Scope) -> View {
|
||||
let Self { id, child_fn } = self;
|
||||
// concrete inner function
|
||||
fn create_dyn_view(
|
||||
cx: Scope,
|
||||
component: DynChildRepr,
|
||||
child_fn: Box<dyn Fn() -> View>,
|
||||
) -> DynChildRepr {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
let closing = component.closing.node.clone();
|
||||
|
||||
let component = DynChildRepr::new_with_id(id);
|
||||
let child = component.child.clone();
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
let closing = component.closing.node.clone();
|
||||
#[cfg(all(debug_assertions, target_arch = "wasm32", feature = "web"))]
|
||||
let span = tracing::Span::current();
|
||||
|
||||
let child = component.child.clone();
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
create_effect(
|
||||
cx,
|
||||
move |prev_run: Option<(Option<web_sys::Node>, ScopeDisposer)>| {
|
||||
#[cfg(debug_assertions)]
|
||||
let _guard = span.enter();
|
||||
|
||||
#[cfg(all(debug_assertions, target_arch = "wasm32", feature = "web"))]
|
||||
let span = tracing::Span::current();
|
||||
let (new_child, disposer) =
|
||||
cx.run_child_scope(|cx| child_fn().into_view(cx));
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
create_effect(
|
||||
cx,
|
||||
move |prev_run: Option<(Option<web_sys::Node>, ScopeDisposer)>| {
|
||||
#[cfg(debug_assertions)]
|
||||
let _guard = span.enter();
|
||||
let mut child_borrow = child.borrow_mut();
|
||||
|
||||
let (new_child, disposer) =
|
||||
cx.run_child_scope(|cx| child_fn().into_view(cx));
|
||||
// Is this at least the second time we are loading a child?
|
||||
if let Some((prev_t, prev_disposer)) = prev_run {
|
||||
let child = child_borrow.take().unwrap();
|
||||
|
||||
let mut child_borrow = child.borrow_mut();
|
||||
// Dispose of the scope
|
||||
prev_disposer.dispose();
|
||||
|
||||
// Is this at least the second time we are loading a child?
|
||||
if let Some((prev_t, prev_disposer)) = prev_run {
|
||||
let child = child_borrow.take().unwrap();
|
||||
// We need to know if our child wasn't moved elsewhere.
|
||||
// If it was, `DynChild` no longer "owns" that child, and
|
||||
// is therefore no longer sound to unmount it from the DOM
|
||||
// or to reuse it in the case of a text node
|
||||
|
||||
// Dispose of the scope
|
||||
prev_disposer.dispose();
|
||||
// TODO check does this still detect moves correctly?
|
||||
let was_child_moved = prev_t.is_none()
|
||||
&& child.get_closing_node().next_sibling().as_ref()
|
||||
!= Some(&closing);
|
||||
|
||||
// We need to know if our child wasn't moved elsewhere.
|
||||
// If it was, `DynChild` no longer "owns" that child, and
|
||||
// is therefore no longer sound to unmount it from the DOM
|
||||
// or to reuse it in the case of a text node
|
||||
// If the previous child was a text node, we would like to
|
||||
// make use of it again if our current child is also a text
|
||||
// node
|
||||
let ret = if let Some(prev_t) = prev_t {
|
||||
// Here, our child is also a text node
|
||||
if let Some(new_t) = new_child.get_text() {
|
||||
if !was_child_moved && child != new_child {
|
||||
prev_t
|
||||
.unchecked_ref::<web_sys::Text>()
|
||||
.set_data(&new_t.content);
|
||||
|
||||
// TODO check does this still detect moves correctly?
|
||||
let was_child_moved = prev_t.is_none()
|
||||
&& child.get_closing_node().next_sibling().as_ref()
|
||||
!= Some(&closing);
|
||||
**child_borrow = Some(new_child);
|
||||
|
||||
// If the previous child was a text node, we would like to
|
||||
// make use of it again if our current child is also a text
|
||||
// node
|
||||
let ret = if let Some(prev_t) = prev_t {
|
||||
// Here, our child is also a text node
|
||||
if let Some(new_t) = new_child.get_text() {
|
||||
if !was_child_moved && child != new_child {
|
||||
prev_t
|
||||
.unchecked_ref::<web_sys::Text>()
|
||||
.set_data(&new_t.content);
|
||||
(Some(prev_t), disposer)
|
||||
} else {
|
||||
mount_child(MountKind::Before(&closing), &new_child);
|
||||
|
||||
**child_borrow = Some(new_child.clone());
|
||||
|
||||
(Some(new_t.node.clone()), disposer)
|
||||
}
|
||||
}
|
||||
// Child is not a text node, so we can remove the previous
|
||||
// text node
|
||||
else {
|
||||
if !was_child_moved && child != new_child {
|
||||
// Remove the text
|
||||
closing
|
||||
.previous_sibling()
|
||||
.unwrap()
|
||||
.unchecked_into::<web_sys::Element>()
|
||||
.remove();
|
||||
}
|
||||
|
||||
// Mount the new child, and we're done
|
||||
mount_child(MountKind::Before(&closing), &new_child);
|
||||
|
||||
**child_borrow = Some(new_child);
|
||||
|
||||
(Some(prev_t), disposer)
|
||||
} else {
|
||||
mount_child(MountKind::Before(&closing), &new_child);
|
||||
|
||||
**child_borrow = Some(new_child.clone());
|
||||
|
||||
(Some(new_t.node.clone()), disposer)
|
||||
(None, disposer)
|
||||
}
|
||||
}
|
||||
// Child is not a text node, so we can remove the previous
|
||||
// text node
|
||||
// Otherwise, the new child can still be a text node,
|
||||
// but we know the previous child was not, so no special
|
||||
// treatment here
|
||||
else {
|
||||
if !was_child_moved && child != new_child {
|
||||
// Remove the text
|
||||
closing
|
||||
.previous_sibling()
|
||||
// Technically, I think this check shouldn't be necessary, but
|
||||
// I can imagine some edge case that the child changes while
|
||||
// hydration is ongoing
|
||||
if !HydrationCtx::is_hydrating() {
|
||||
if !was_child_moved && child != new_child {
|
||||
// Remove the child
|
||||
let start = child.get_opening_node();
|
||||
let end = &closing;
|
||||
|
||||
unmount_child(&start, end);
|
||||
}
|
||||
|
||||
// Mount the new child
|
||||
mount_child(MountKind::Before(&closing), &new_child);
|
||||
}
|
||||
|
||||
// We want to reuse text nodes, so hold onto it if
|
||||
// our child is one
|
||||
let t = new_child.get_text().map(|t| t.node.clone());
|
||||
|
||||
**child_borrow = Some(new_child);
|
||||
|
||||
(t, disposer)
|
||||
};
|
||||
|
||||
ret
|
||||
}
|
||||
// Otherwise, we know for sure this is our first time
|
||||
else {
|
||||
// We need to remove the text created from SSR
|
||||
if HydrationCtx::is_hydrating() && new_child.get_text().is_some() {
|
||||
let t = closing
|
||||
.previous_sibling()
|
||||
.unwrap()
|
||||
.unchecked_into::<web_sys::Element>();
|
||||
|
||||
// See note on ssr.rs when matching on `DynChild`
|
||||
// for more details on why we need to do this for
|
||||
// release
|
||||
if !cfg!(debug_assertions) {
|
||||
t.previous_sibling()
|
||||
.unwrap()
|
||||
.unchecked_into::<web_sys::Element>()
|
||||
.remove();
|
||||
}
|
||||
|
||||
// Mount the new child, and we're done
|
||||
t.remove();
|
||||
|
||||
mount_child(MountKind::Before(&closing), &new_child);
|
||||
|
||||
**child_borrow = Some(new_child);
|
||||
|
||||
(None, disposer)
|
||||
}
|
||||
}
|
||||
// Otherwise, the new child can still be a text node,
|
||||
// but we know the previous child was not, so no special
|
||||
// treatment here
|
||||
else {
|
||||
// Technically, I think this check shouldn't be necessary, but
|
||||
// I can imagine some edge case that the child changes while
|
||||
// hydration is ongoing
|
||||
|
||||
// If we are not hydrating, we simply mount the child
|
||||
if !HydrationCtx::is_hydrating() {
|
||||
if !was_child_moved && child != new_child {
|
||||
// Remove the child
|
||||
let start = child.get_opening_node();
|
||||
let end = &closing;
|
||||
|
||||
unmount_child(&start, end);
|
||||
}
|
||||
|
||||
// Mount the new child
|
||||
mount_child(MountKind::Before(&closing), &new_child);
|
||||
}
|
||||
|
||||
// We want to reuse text nodes, so hold onto it if
|
||||
// our child is one
|
||||
// We want to update text nodes, rather than replace them, so
|
||||
// make sure to hold onto the text node
|
||||
let t = new_child.get_text().map(|t| t.node.clone());
|
||||
|
||||
**child_borrow = Some(new_child);
|
||||
|
||||
(t, disposer)
|
||||
};
|
||||
|
||||
ret
|
||||
}
|
||||
// Otherwise, we know for sure this is our first time
|
||||
else {
|
||||
// We need to remove the text created from SSR
|
||||
if HydrationCtx::is_hydrating() && new_child.get_text().is_some() {
|
||||
let t = closing
|
||||
.previous_sibling()
|
||||
.unwrap()
|
||||
.unchecked_into::<web_sys::Element>();
|
||||
|
||||
// See note on ssr.rs when matching on `DynChild`
|
||||
// for more details on why we need to do this for
|
||||
// release
|
||||
if !cfg!(debug_assertions) {
|
||||
t.previous_sibling()
|
||||
.unwrap()
|
||||
.unchecked_into::<web_sys::Element>()
|
||||
.remove();
|
||||
}
|
||||
|
||||
t.remove();
|
||||
|
||||
mount_child(MountKind::Before(&closing), &new_child);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// If we are not hydrating, we simply mount the child
|
||||
if !HydrationCtx::is_hydrating() {
|
||||
mount_child(MountKind::Before(&closing), &new_child);
|
||||
}
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
{
|
||||
let new_child = child_fn().into_view(cx);
|
||||
|
||||
// We want to update text nodes, rather than replace them, so
|
||||
// make sure to hold onto the text node
|
||||
let t = new_child.get_text().map(|t| t.node.clone());
|
||||
**child.borrow_mut() = Some(new_child);
|
||||
}
|
||||
|
||||
**child_borrow = Some(new_child);
|
||||
|
||||
(t, disposer)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
{
|
||||
let new_child = child_fn().into_view(cx);
|
||||
|
||||
**child.borrow_mut() = Some(new_child);
|
||||
component
|
||||
}
|
||||
|
||||
// monomorphized outer function
|
||||
let Self { id, child_fn } = self;
|
||||
|
||||
let component = DynChildRepr::new_with_id(id);
|
||||
let component = create_dyn_view(
|
||||
cx,
|
||||
component,
|
||||
Box::new(move || child_fn().into_view(cx)),
|
||||
);
|
||||
|
||||
View::CoreComponent(crate::CoreComponent::DynChild(component))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -16,7 +16,8 @@ thread_local! {
|
|||
pub fn add_event_listener<E>(
|
||||
target: &web_sys::Element,
|
||||
event_name: Cow<'static, str>,
|
||||
mut cb: impl FnMut(E) + 'static,
|
||||
#[cfg(debug_assertions)] mut cb: impl FnMut(E) + 'static,
|
||||
#[cfg(not(debug_assertions))] cb: impl FnMut(E) + 'static,
|
||||
) where
|
||||
E: FromWasmAbi + 'static,
|
||||
{
|
||||
|
|
|
@ -200,7 +200,7 @@ where
|
|||
}
|
||||
|
||||
impl EffectId {
|
||||
pub(crate) fn run<T>(&self, runtime_id: RuntimeId) {
|
||||
pub(crate) fn run(&self, runtime_id: RuntimeId) {
|
||||
_ = with_runtime(runtime_id, |runtime| {
|
||||
let effect = {
|
||||
let effects = runtime.effects.borrow();
|
||||
|
|
|
@ -130,7 +130,8 @@ where
|
|||
});
|
||||
|
||||
let id = with_runtime(cx.runtime, |runtime| {
|
||||
runtime.create_serializable_resource(Rc::clone(&r))
|
||||
let r = Rc::clone(&r) as Rc<dyn SerializableResource>;
|
||||
runtime.create_serializable_resource(r)
|
||||
})
|
||||
.expect("tried to create a Resource in a Runtime that has been disposed.");
|
||||
|
||||
|
@ -250,7 +251,8 @@ where
|
|||
});
|
||||
|
||||
let id = with_runtime(cx.runtime, |runtime| {
|
||||
runtime.create_unserializable_resource(Rc::clone(&r))
|
||||
let r = Rc::clone(&r) as Rc<dyn UnserializableResource>;
|
||||
runtime.create_unserializable_resource(r)
|
||||
})
|
||||
.expect("tried to create a Resource in a runtime that has been disposed.");
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#![forbid(unsafe_code)]
|
||||
use crate::{
|
||||
hydration::SharedContext, serialization::Serializable, AnyEffect, AnyResource, Effect,
|
||||
EffectId, Memo, ReadSignal, ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer, ScopeId,
|
||||
ScopeProperty, SignalId, WriteSignal,
|
||||
hydration::SharedContext, AnyEffect, AnyResource, Effect, EffectId, Memo, ReadSignal,
|
||||
ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer, ScopeId, ScopeProperty,
|
||||
SerializableResource, SignalId, UnserializableResource, WriteSignal,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use futures::stream::FuturesUnordered;
|
||||
|
@ -115,18 +115,19 @@ impl RuntimeId {
|
|||
ret
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn create_concrete_signal(self, value: Rc<RefCell<dyn Any>>) -> SignalId {
|
||||
with_runtime(self, |runtime| runtime.signals.borrow_mut().insert(value))
|
||||
.expect("tried to create a signal in a runtime that has been disposed")
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn create_signal<T>(self, value: T) -> (ReadSignal<T>, WriteSignal<T>)
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = with_runtime(self, |runtime| {
|
||||
runtime
|
||||
.signals
|
||||
.borrow_mut()
|
||||
.insert(Rc::new(RefCell::new(value)))
|
||||
})
|
||||
.expect("tried to create a signal in a runtime that has been disposed");
|
||||
let id = self.create_concrete_signal(Rc::new(RefCell::new(value)) as Rc<RefCell<dyn Any>>);
|
||||
|
||||
(
|
||||
ReadSignal {
|
||||
runtime: self,
|
||||
|
@ -149,13 +150,7 @@ impl RuntimeId {
|
|||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = with_runtime(self, |runtime| {
|
||||
runtime
|
||||
.signals
|
||||
.borrow_mut()
|
||||
.insert(Rc::new(RefCell::new(value)))
|
||||
})
|
||||
.expect("tried to create a signal in a runtime that has been disposed");
|
||||
let id = self.create_concrete_signal(Rc::new(RefCell::new(value)) as Rc<RefCell<dyn Any>>);
|
||||
RwSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
|
@ -165,6 +160,12 @@ impl RuntimeId {
|
|||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn create_concrete_effect(self, effect: Rc<dyn AnyEffect>) -> EffectId {
|
||||
with_runtime(self, |runtime| runtime.effects.borrow_mut().insert(effect))
|
||||
.expect("tried to create an effect in a runtime that has been disposed")
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn create_effect<T>(self, f: impl Fn(Option<T>) -> T + 'static) -> EffectId
|
||||
where
|
||||
|
@ -173,18 +174,16 @@ impl RuntimeId {
|
|||
#[cfg(debug_assertions)]
|
||||
let defined_at = std::panic::Location::caller();
|
||||
|
||||
with_runtime(self, |runtime| {
|
||||
let effect = Effect {
|
||||
f,
|
||||
value: RefCell::new(None),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
};
|
||||
let id = { runtime.effects.borrow_mut().insert(Rc::new(effect)) };
|
||||
id.run::<T>(self);
|
||||
id
|
||||
})
|
||||
.expect("tried to create an effect in a runtime that has been disposed")
|
||||
let effect = Effect {
|
||||
f,
|
||||
value: RefCell::new(None),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
};
|
||||
|
||||
let id = self.create_concrete_effect(Rc::new(effect));
|
||||
id.run(self);
|
||||
id
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
@ -256,27 +255,19 @@ impl Runtime {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
pub(crate) fn create_unserializable_resource<S, T>(
|
||||
pub(crate) fn create_unserializable_resource(
|
||||
&self,
|
||||
state: Rc<ResourceState<S, T>>,
|
||||
) -> ResourceId
|
||||
where
|
||||
S: Clone + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
state: Rc<dyn UnserializableResource>,
|
||||
) -> ResourceId {
|
||||
self.resources
|
||||
.borrow_mut()
|
||||
.insert(AnyResource::Unserializable(state))
|
||||
}
|
||||
|
||||
pub(crate) fn create_serializable_resource<S, T>(
|
||||
pub(crate) fn create_serializable_resource(
|
||||
&self,
|
||||
state: Rc<ResourceState<S, T>>,
|
||||
) -> ResourceId
|
||||
where
|
||||
S: Clone + 'static,
|
||||
T: Serializable + 'static,
|
||||
{
|
||||
state: Rc<dyn SerializableResource>,
|
||||
) -> ResourceId {
|
||||
self.resources
|
||||
.borrow_mut()
|
||||
.insert(AnyResource::Serializable(state))
|
||||
|
|
|
@ -43,86 +43,109 @@ pub fn Form<A>(
|
|||
where
|
||||
A: ToHref + 'static,
|
||||
{
|
||||
let action_version = version;
|
||||
let action = use_resolved_path(cx, move || action.to_href()());
|
||||
fn inner(
|
||||
cx: Scope,
|
||||
method: Option<&'static str>,
|
||||
action: Memo<Option<String>>,
|
||||
enctype: Option<String>,
|
||||
version: Option<RwSignal<usize>>,
|
||||
error: Option<RwSignal<Option<Box<dyn Error>>>>,
|
||||
#[allow(clippy::type_complexity)] on_form_data: Option<Rc<dyn Fn(&web_sys::FormData)>>,
|
||||
#[allow(clippy::type_complexity)] on_response: Option<Rc<dyn Fn(&web_sys::Response)>>,
|
||||
children: Children,
|
||||
) -> HtmlElement<Form> {
|
||||
let action_version = version;
|
||||
let on_submit = move |ev: web_sys::SubmitEvent| {
|
||||
if ev.default_prevented() {
|
||||
return;
|
||||
}
|
||||
let navigate = use_navigate(cx);
|
||||
|
||||
let on_submit = move |ev: web_sys::SubmitEvent| {
|
||||
if ev.default_prevented() {
|
||||
return;
|
||||
}
|
||||
let navigate = use_navigate(cx);
|
||||
let (form, method, action, enctype) = extract_form_attributes(&ev);
|
||||
|
||||
let (form, method, action, enctype) = extract_form_attributes(&ev);
|
||||
let form_data = web_sys::FormData::new_with_form(&form).unwrap_throw();
|
||||
if let Some(on_form_data) = on_form_data.clone() {
|
||||
on_form_data(&form_data);
|
||||
}
|
||||
let params =
|
||||
web_sys::UrlSearchParams::new_with_str_sequence_sequence(&form_data).unwrap_throw();
|
||||
let action = use_resolved_path(cx, move || action.clone())
|
||||
.get()
|
||||
.unwrap_or_default();
|
||||
// POST
|
||||
if method == "post" {
|
||||
ev.prevent_default();
|
||||
|
||||
let form_data = web_sys::FormData::new_with_form(&form).unwrap_throw();
|
||||
if let Some(on_form_data) = on_form_data.clone() {
|
||||
on_form_data(&form_data);
|
||||
}
|
||||
let params =
|
||||
web_sys::UrlSearchParams::new_with_str_sequence_sequence(&form_data).unwrap_throw();
|
||||
let action = use_resolved_path(cx, move || action.clone())
|
||||
.get()
|
||||
.unwrap_or_default();
|
||||
// POST
|
||||
if method == "post" {
|
||||
ev.prevent_default();
|
||||
|
||||
let on_response = on_response.clone();
|
||||
spawn_local(async move {
|
||||
let res = gloo_net::http::Request::post(&action)
|
||||
.header("Accept", "application/json")
|
||||
.header("Content-Type", &enctype)
|
||||
.body(params)
|
||||
.send()
|
||||
.await;
|
||||
match res {
|
||||
Err(e) => {
|
||||
log::error!("<Form/> error while POSTing: {e:#?}");
|
||||
if let Some(error) = error {
|
||||
error.set(Some(Box::new(e)));
|
||||
}
|
||||
}
|
||||
Ok(resp) => {
|
||||
if let Some(version) = action_version {
|
||||
version.update(|n| *n += 1);
|
||||
}
|
||||
if let Some(error) = error {
|
||||
error.set(None);
|
||||
}
|
||||
if let Some(on_response) = on_response.clone() {
|
||||
on_response(resp.as_raw());
|
||||
let on_response = on_response.clone();
|
||||
spawn_local(async move {
|
||||
let res = gloo_net::http::Request::post(&action)
|
||||
.header("Accept", "application/json")
|
||||
.header("Content-Type", &enctype)
|
||||
.body(params)
|
||||
.send()
|
||||
.await;
|
||||
match res {
|
||||
Err(e) => {
|
||||
log::error!("<Form/> error while POSTing: {e:#?}");
|
||||
if let Some(error) = error {
|
||||
error.set(Some(Box::new(e)));
|
||||
}
|
||||
}
|
||||
Ok(resp) => {
|
||||
if let Some(version) = action_version {
|
||||
version.update(|n| *n += 1);
|
||||
}
|
||||
if let Some(error) = error {
|
||||
error.set(None);
|
||||
}
|
||||
if let Some(on_response) = on_response.clone() {
|
||||
on_response(resp.as_raw());
|
||||
}
|
||||
|
||||
if resp.status() == 303 {
|
||||
if let Some(redirect_url) = resp.headers().get("Location") {
|
||||
_ = navigate(&redirect_url, Default::default());
|
||||
if resp.status() == 303 {
|
||||
if let Some(redirect_url) = resp.headers().get("Location") {
|
||||
_ = navigate(&redirect_url, Default::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// otherwise, GET
|
||||
else {
|
||||
let params = params.to_string().as_string().unwrap_or_default();
|
||||
if navigate(&format!("{action}?{params}"), Default::default()).is_ok() {
|
||||
ev.prevent_default();
|
||||
});
|
||||
}
|
||||
// otherwise, GET
|
||||
else {
|
||||
let params = params.to_string().as_string().unwrap_or_default();
|
||||
if navigate(&format!("{action}?{params}"), Default::default()).is_ok() {
|
||||
ev.prevent_default();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let method = method.unwrap_or("get");
|
||||
|
||||
view! { cx,
|
||||
<form
|
||||
method=method
|
||||
action=move || action.get()
|
||||
enctype=enctype
|
||||
on:submit=on_submit
|
||||
>
|
||||
{children(cx)}
|
||||
</form>
|
||||
}
|
||||
};
|
||||
|
||||
let method = method.unwrap_or("get");
|
||||
|
||||
view! { cx,
|
||||
<form
|
||||
method=method
|
||||
action=move || action.get()
|
||||
enctype=enctype
|
||||
on:submit=on_submit
|
||||
>
|
||||
{children(cx)}
|
||||
</form>
|
||||
}
|
||||
|
||||
let action = use_resolved_path(cx, move || action.to_href()());
|
||||
inner(
|
||||
cx,
|
||||
method,
|
||||
action,
|
||||
enctype,
|
||||
version,
|
||||
error,
|
||||
on_form_data,
|
||||
on_response,
|
||||
children,
|
||||
)
|
||||
}
|
||||
|
||||
/// Automatically turns a server [Action](leptos_server::Action) into an HTML
|
||||
|
|
|
@ -70,35 +70,47 @@ pub fn A<H>(
|
|||
where
|
||||
H: ToHref + 'static,
|
||||
{
|
||||
let location = use_location(cx);
|
||||
let href = use_resolved_path(cx, move || href.to_href()());
|
||||
let is_active = create_memo(cx, move |_| match href.get() {
|
||||
None => false,
|
||||
fn inner(
|
||||
cx: Scope,
|
||||
href: Memo<Option<String>>,
|
||||
exact: bool,
|
||||
state: Option<State>,
|
||||
replace: bool,
|
||||
class: Option<MaybeSignal<String>>,
|
||||
children: Children,
|
||||
) -> HtmlElement<A> {
|
||||
let location = use_location(cx);
|
||||
let is_active = create_memo(cx, move |_| match href.get() {
|
||||
None => false,
|
||||
|
||||
Some(to) => {
|
||||
let path = to
|
||||
.split(['?', '#'])
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
let loc = location.pathname.get().to_lowercase();
|
||||
if exact {
|
||||
loc == path
|
||||
} else {
|
||||
loc.starts_with(&path)
|
||||
Some(to) => {
|
||||
let path = to
|
||||
.split(['?', '#'])
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
let loc = location.pathname.get().to_lowercase();
|
||||
if exact {
|
||||
loc == path
|
||||
} else {
|
||||
loc.starts_with(&path)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
view! { cx,
|
||||
<a
|
||||
href=move || href.get().unwrap_or_default()
|
||||
prop:state={state.map(|s| s.to_js_value())}
|
||||
prop:replace={replace}
|
||||
aria-current=move || if is_active.get() { Some("page") } else { None }
|
||||
class=move || class.as_ref().map(|class| class.get())
|
||||
>
|
||||
{children(cx)}
|
||||
</a>
|
||||
view! { cx,
|
||||
<a
|
||||
href=move || href.get().unwrap_or_default()
|
||||
prop:state={state.map(|s| s.to_js_value())}
|
||||
prop:replace={replace}
|
||||
aria-current=move || if is_active.get() { Some("page") } else { None }
|
||||
class=move || class.as_ref().map(|class| class.get())
|
||||
>
|
||||
{children(cx)}
|
||||
</a>
|
||||
}
|
||||
}
|
||||
|
||||
let href = use_resolved_path(cx, move || href.to_href()());
|
||||
inner(cx, href, exact, state, replace, class, children)
|
||||
}
|
||||
|
|
|
@ -36,31 +36,47 @@ where
|
|||
F: Fn(Scope) -> E + 'static,
|
||||
P: std::fmt::Display,
|
||||
{
|
||||
let children = children
|
||||
.map(|children| {
|
||||
children(cx)
|
||||
.as_children()
|
||||
.iter()
|
||||
.filter_map(|child| {
|
||||
child
|
||||
.as_transparent()
|
||||
.and_then(|t| t.downcast_ref::<RouteDefinition>())
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let id = ROUTE_ID.with(|id| {
|
||||
let next = id.get() + 1;
|
||||
id.set(next);
|
||||
next
|
||||
});
|
||||
RouteDefinition {
|
||||
id,
|
||||
path: path.to_string(),
|
||||
children,
|
||||
view: Rc::new(move |cx| view(cx).into_view(cx)),
|
||||
fn inner(
|
||||
cx: Scope,
|
||||
children: Option<Children>,
|
||||
path: String,
|
||||
view: Rc<dyn Fn(Scope) -> View>,
|
||||
) -> RouteDefinition {
|
||||
let children = children
|
||||
.map(|children| {
|
||||
children(cx)
|
||||
.as_children()
|
||||
.iter()
|
||||
.filter_map(|child| {
|
||||
child
|
||||
.as_transparent()
|
||||
.and_then(|t| t.downcast_ref::<RouteDefinition>())
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let id = ROUTE_ID.with(|id| {
|
||||
let next = id.get() + 1;
|
||||
id.set(next);
|
||||
next
|
||||
});
|
||||
|
||||
RouteDefinition {
|
||||
id,
|
||||
path,
|
||||
children,
|
||||
view,
|
||||
}
|
||||
}
|
||||
|
||||
inner(
|
||||
cx,
|
||||
children,
|
||||
path.to_string(),
|
||||
Rc::new(move |cx| view(cx).into_view(cx)),
|
||||
)
|
||||
}
|
||||
|
||||
impl IntoView for RouteDefinition {
|
||||
|
|
|
@ -8,10 +8,6 @@
|
|||
//! apps (SPAs), server-side rendering/multi-page apps (MPAs), or to synchronize
|
||||
//! state between the two.
|
||||
//!
|
||||
//! **Note:** This is a work in progress. The feature to pass client-side route [State] in
|
||||
//! [History.state](https://developer.mozilla.org/en-US/docs/Web/API/History/state), in particular,
|
||||
//! is incomplete.
|
||||
//!
|
||||
//! ## Philosophy
|
||||
//!
|
||||
//! Leptos Router is built on a few simple principles:
|
||||
|
@ -23,12 +19,7 @@
|
|||
//! and are rendered by different components. This means you can navigate between siblings
|
||||
//! in this tree without re-rendering or triggering any change in the parent routes.
|
||||
//!
|
||||
//! 3. **Route-based data loading.** Each route should know exactly which data it needs
|
||||
//! to render itself when the route is defined. This allows each route’s data to be
|
||||
//! reloaded independently, and allows data from nested routes to be loaded in parallel,
|
||||
//! avoiding waterfalls.
|
||||
//!
|
||||
//! 4. **Progressive enhancement.** The [A] and [Form] components resolve any relative
|
||||
//! 3. **Progressive enhancement.** The [A] and [Form] components resolve any relative
|
||||
//! nested routes, render actual `<a>` and `<form>` elements, and (when possible)
|
||||
//! upgrading them to handle those navigations with client-side routing. If you’re using
|
||||
//! them with server-side rendering (with or without hydration), they just work,
|
||||
|
|
Loading…
Reference in a new issue