Răsfoiți Sursa

make serde optional

Evan Almloff 2 ani în urmă
părinte
comite
2f473f7c97

+ 5 - 3
packages/router/Cargo.toml

@@ -16,17 +16,19 @@ dioxus = { path="../dioxus" }
 dioxus-router-macro = { path="../router-macro" }
 gloo = { version = "0.8.0", optional = true }
 log = "0.4.17"
-serde = { version = "1.0.163", features = ["derive"] }
+serde = { version = "1.0.163", features = ["derive"], optional = true }
 thiserror = "1.0.37"
 url = "2.3.1"
 wasm-bindgen = { version = "0.2.86", optional = true }
 web-sys = { version = "0.3.60", optional = true, features = ["ScrollRestoration"] }
-gloo-utils = { version = "0.1.6", optional = true, features = ["serde"] }
+js-sys = { version = "0.3.63", optional = true }
+gloo-utils = { version = "0.1.6", optional = true }
 
 [features]
 default = ["web"]
 wasm_test = []
-web = ["gloo", "web-sys", "wasm-bindgen", "gloo-utils"]
+serde = ["dep:serde", "gloo-utils/serde"]
+web = ["gloo", "web-sys", "wasm-bindgen", "gloo-utils", "js-sys"]
 
 [dev-dependencies]
 dioxus = { path = "../dioxus" }

+ 1 - 2
packages/router/examples/simple_routes.rs

@@ -2,7 +2,6 @@
 
 use dioxus::prelude::*;
 use dioxus_router::prelude::*;
-use serde::{Deserialize, Serialize};
 use std::str::FromStr;
 
 fn main() {
@@ -128,7 +127,7 @@ fn Route3(cx: Scope, dynamic: String) -> Element {
 }
 
 #[rustfmt::skip]
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Routable)]
+#[derive(Clone, Debug, PartialEq, Routable)]
 enum Route {
     // Nests with parameters have types taken from child routes
     #[nest("/user/:user_id")]

+ 4 - 7
packages/router/src/components/router.rs

@@ -1,6 +1,5 @@
 use dioxus::prelude::*;
 use log::error;
-use serde::{de::DeserializeOwned, Serialize};
 use std::{cell::RefCell, str::FromStr};
 
 use crate::{
@@ -14,7 +13,7 @@ pub struct RouterCfg<R: Routable> {
     config: RefCell<Option<RouterConfiguration<R>>>,
 }
 
-impl<R: Routable + Serialize + DeserializeOwned> Default for RouterCfg<R>
+impl<R: Routable> Default for RouterCfg<R>
 where
     <R as FromStr>::Err: std::fmt::Display,
 {
@@ -35,7 +34,7 @@ impl<R: Routable> From<RouterConfiguration<R>> for RouterCfg<R> {
 
 /// The props for [`GenericRouter`].
 #[derive(Props)]
-pub struct GenericRouterProps<R: Routable + Serialize + DeserializeOwned>
+pub struct GenericRouterProps<R: Routable>
 where
     <R as FromStr>::Err: std::fmt::Display,
 {
@@ -43,7 +42,7 @@ where
     config: RouterCfg<R>,
 }
 
-impl<R: Routable + Serialize + DeserializeOwned> PartialEq for GenericRouterProps<R>
+impl<R: Routable> PartialEq for GenericRouterProps<R>
 where
     <R as FromStr>::Err: std::fmt::Display,
 {
@@ -54,9 +53,7 @@ where
 }
 
 /// A component that renders the current route.
-pub fn GenericRouter<R: Routable + Clone + Serialize + DeserializeOwned>(
-    cx: Scope<GenericRouterProps<R>>,
-) -> Element
+pub fn GenericRouter<R: Routable + Clone>(cx: Scope<GenericRouterProps<R>>) -> Element
 where
     <R as FromStr>::Err: std::fmt::Display,
 {

+ 218 - 60
packages/router/src/history/web.rs

@@ -1,7 +1,7 @@
 use std::sync::{Arc, Mutex};
 
 use gloo::{console::error, events::EventListener, render::AnimationFrame};
-use serde::{de::DeserializeOwned, Deserialize, Serialize};
+use wasm_bindgen::JsValue;
 use web_sys::{window, History, ScrollRestoration, Window};
 
 use crate::routable::Routable;
@@ -12,7 +12,20 @@ use super::{
     HistoryProvider,
 };
 
-fn update_scroll<R: Serialize + DeserializeOwned + Routable>(window: &Window, history: &History) {
+#[cfg(not(feature = "serde"))]
+#[allow(clippy::extra_unused_type_parameters)]
+fn update_scroll<R>(window: &Window, history: &History) {
+    let scroll = ScrollPosition::of_window(window);
+    if let Err(err) = replace_state_with_url(history, &[scroll.x, scroll.y], None) {
+        error!(err);
+    }
+}
+
+#[cfg(feature = "serde")]
+fn update_scroll<R: serde::Serialize + serde::de::DeserializeOwned + Routable>(
+    window: &Window,
+    history: &History,
+) {
     if let Some(WebHistoryState { state, .. }) = get_current::<WebHistoryState<R>>(history) {
         let scroll = ScrollPosition::of_window(window);
         let state = WebHistoryState { state, scroll };
@@ -22,7 +35,8 @@ fn update_scroll<R: Serialize + DeserializeOwned + Routable>(window: &Window, hi
     }
 }
 
-#[derive(Deserialize, Serialize)]
+#[cfg(feature = "serde")]
+#[derive(serde::Deserialize, serde::Serialize)]
 struct WebHistoryState<R> {
     state: R,
     scroll: ScrollPosition,
@@ -40,7 +54,7 @@ struct WebHistoryState<R> {
 ///
 /// Application developers are responsible for not rendering the router if the prefix is not present
 /// in the URL. Otherwise, if a router navigation is triggered, the prefix will be added.
-pub struct WebHistory<R: Serialize + DeserializeOwned + Routable> {
+pub struct WebHistory<R: Routable> {
     do_scroll_restoration: bool,
     history: History,
     listener_navigation: Option<EventListener>,
@@ -52,21 +66,92 @@ pub struct WebHistory<R: Serialize + DeserializeOwned + Routable> {
     phantom: std::marker::PhantomData<R>,
 }
 
-impl<R: Serialize + DeserializeOwned + Routable> Default for WebHistory<R>
+#[cfg(not(feature = "serde"))]
+impl<R: Routable> Default for WebHistory<R>
+where
+    <R as std::str::FromStr>::Err: std::fmt::Display,
+{
+    fn default() -> Self {
+        Self::new(None, true)
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<R: Routable> Default for WebHistory<R>
 where
     <R as std::str::FromStr>::Err: std::fmt::Display,
+    R: serde::Serialize + serde::de::DeserializeOwned,
 {
     fn default() -> Self {
         Self::new(None, true)
     }
 }
 
-impl<R: Serialize + DeserializeOwned + Routable> WebHistory<R> {
+impl<R: Routable> WebHistory<R> {
+    #[cfg(not(feature = "serde"))]
     /// Create a new [`WebHistory`].
     ///
     /// If `do_scroll_restoration` is [`true`], [`WebHistory`] will take control of the history
     /// state. It'll also set the browsers scroll restoration to `manual`.
-    pub fn new(prefix: Option<String>, do_scroll_restoration: bool) -> Self
+    fn new(prefix: Option<String>, do_scroll_restoration: bool) -> Self
+    where
+        <R as std::str::FromStr>::Err: std::fmt::Display,
+    {
+        let w = window().expect("access to `window`");
+        let h = w.history().expect("`window` has access to `history`");
+        let document = w.document().expect("`window` has access to `document`");
+
+        let myself = Self::new_inner(
+            prefix,
+            do_scroll_restoration,
+            EventListener::new(&document, "scroll", move |_| {
+                update_scroll::<R>(&w, &h);
+            }),
+        );
+
+        let current_route = myself.current_route();
+        let current_url = current_route.to_string();
+        let state = myself.create_state(current_route);
+        let _ = replace_state_with_url(&myself.history, &state, Some(&current_url));
+
+        myself
+    }
+
+    #[cfg(feature = "serde")]
+    /// Create a new [`WebHistory`].
+    ///
+    /// If `do_scroll_restoration` is [`true`], [`WebHistory`] will take control of the history
+    /// state. It'll also set the browsers scroll restoration to `manual`.
+    fn new(prefix: Option<String>, do_scroll_restoration: bool) -> Self
+    where
+        <R as std::str::FromStr>::Err: std::fmt::Display,
+        R: serde::Serialize + serde::de::DeserializeOwned,
+    {
+        let w = window().expect("access to `window`");
+        let h = w.history().expect("`window` has access to `history`");
+        let document = w.document().expect("`window` has access to `document`");
+
+        let myself = Self::new_inner(
+            prefix,
+            do_scroll_restoration,
+            EventListener::new(&document, "scroll", move |_| {
+                update_scroll::<R>(&w, &h);
+            }),
+        );
+
+        let current_route = myself.current_route();
+        let current_url = current_route.to_string();
+        let state = myself.create_state(current_route);
+        let _ = replace_state_with_url(&myself.history, &state, Some(&current_url));
+
+        myself
+    }
+
+    fn new_inner(
+        prefix: Option<String>,
+        do_scroll_restoration: bool,
+        event_listener: EventListener,
+    ) -> Self
     where
         <R as std::str::FromStr>::Err: std::fmt::Display,
     {
@@ -78,18 +163,12 @@ impl<R: Serialize + DeserializeOwned + Routable> WebHistory<R> {
                 history
                     .set_scroll_restoration(ScrollRestoration::Manual)
                     .expect("`history` can set scroll restoration");
-                let w = window.clone();
-                let h = history.clone();
-                let document = w.document().expect("`window` has access to `document`");
-
-                Some(EventListener::new(&document, "scroll", move |_| {
-                    update_scroll::<R>(&w, &h);
-                }))
+                Some(event_listener)
             }
             false => None,
         };
 
-        let myself = Self {
+        Self {
             do_scroll_restoration,
             history,
             listener_navigation: None,
@@ -98,26 +177,75 @@ impl<R: Serialize + DeserializeOwned + Routable> WebHistory<R> {
             prefix,
             window,
             phantom: Default::default(),
-        };
+        }
+    }
 
-        let current_route = myself.current_route();
-        let current_url = current_route.to_string();
-        let state = myself.create_state(current_route);
-        let _ = replace_state_with_url(&myself.history, &state, Some(&current_url));
+    fn scroll_pos(&self) -> ScrollPosition {
+        self.do_scroll_restoration
+            .then(|| ScrollPosition::of_window(&self.window))
+            .unwrap_or_default()
+    }
 
-        myself
+    #[cfg(not(feature = "serde"))]
+    fn create_state(&self, _state: R) -> [f64; 2] {
+        let scroll = self.scroll_pos();
+        [scroll.x, scroll.y]
     }
 
+    #[cfg(feature = "serde")]
     fn create_state(&self, state: R) -> WebHistoryState<R> {
-        let scroll = self
-            .do_scroll_restoration
-            .then(|| ScrollPosition::of_window(&self.window))
-            .unwrap_or_default();
+        let scroll = self.scroll_pos();
         WebHistoryState { state, scroll }
     }
 }
 
-impl<R: Serialize + DeserializeOwned + Routable> HistoryProvider<R> for WebHistory<R>
+impl<R: Routable> WebHistory<R>
+where
+    <R as std::str::FromStr>::Err: std::fmt::Display,
+{
+    fn route_from_location(&self) -> R {
+        R::from_str(
+            &self
+                .window
+                .location()
+                .pathname()
+                .unwrap_or_else(|_| String::from("/")),
+        )
+        .unwrap_or_else(|err| panic!("{}", err))
+    }
+
+    fn full_path(&self, state: &R) -> String {
+        match &self.prefix {
+            None => format!("{state}"),
+            Some(prefix) => format!("{prefix}{state}"),
+        }
+    }
+
+    fn handle_nav(&self, result: Result<(), JsValue>) {
+        match result {
+            Ok(_) => {
+                if self.do_scroll_restoration {
+                    self.window.scroll_to_with_x_and_y(0.0, 0.0)
+                }
+            }
+            Err(e) => error!("failed to change state: ", e),
+        }
+    }
+
+    fn navigate_external(&mut self, url: String) -> bool {
+        match self.window.location().set_href(&url) {
+            Ok(_) => true,
+            Err(e) => {
+                error!("failed to navigate to external url (", url, "): ", e);
+                false
+            }
+        }
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<R: serde::Serialize + serde::de::DeserializeOwned + Routable> HistoryProvider<R>
+    for WebHistory<R>
 where
     <R as std::str::FromStr>::Err: std::fmt::Display,
 {
@@ -126,14 +254,7 @@ where
             // Try to get the route from the history state
             Some(route) => route.state,
             // If that fails, get the route from the current URL
-            None => R::from_str(
-                &self
-                    .window
-                    .location()
-                    .pathname()
-                    .unwrap_or_else(|_| String::from("/")),
-            )
-            .unwrap_or_else(|err| panic!("{}", err)),
+            None => self.route_from_location(),
         }
     }
 
@@ -154,6 +275,14 @@ where
     }
 
     fn push(&mut self, state: R) {
+        let path = self.full_path(&state);
+
+        let state = self.create_state(state);
+
+        self.handle_nav(push_state_and_url(&self.history, &state, path));
+    }
+
+    fn replace(&mut self, state: R) {
         let path = match &self.prefix {
             None => format!("{state}"),
             Some(prefix) => format!("{prefix}{state}"),
@@ -161,18 +290,63 @@ where
 
         let state = self.create_state(state);
 
-        let nav = push_state_and_url(&self.history, &state, path);
+        self.handle_nav(replace_state_with_url(&self.history, &state, Some(&path)));
+    }
 
-        match nav {
-            Ok(_) => {
-                if self.do_scroll_restoration {
-                    self.window.scroll_to_with_x_and_y(0.0, 0.0)
+    fn external(&mut self, url: String) -> bool {
+        self.navigate_external(url)
+    }
+
+    fn updater(&mut self, callback: std::sync::Arc<dyn Fn() + Send + Sync>) {
+        let w = self.window.clone();
+        let h = self.history.clone();
+        let s = self.listener_animation_frame.clone();
+        let d = self.do_scroll_restoration;
+
+        self.listener_navigation = Some(EventListener::new(&self.window, "popstate", move |_| {
+            (*callback)();
+            if d {
+                let mut s = s.lock().expect("unpoisoned scroll mutex");
+                if let Some(current_state) = get_current::<WebHistoryState<R>>(&h) {
+                    *s = Some(current_state.scroll.scroll_to(w.clone()));
                 }
             }
-            Err(e) => error!("failed to push state: ", e),
+        }));
+    }
+}
+
+#[cfg(not(feature = "serde"))]
+impl<R: Routable> HistoryProvider<R> for WebHistory<R>
+where
+    <R as std::str::FromStr>::Err: std::fmt::Display,
+{
+    fn current_route(&self) -> R {
+        self.route_from_location()
+    }
+
+    fn current_prefix(&self) -> Option<String> {
+        self.prefix.clone()
+    }
+
+    fn go_back(&mut self) {
+        if let Err(e) = self.history.back() {
+            error!("failed to go back: ", e)
         }
     }
 
+    fn go_forward(&mut self) {
+        if let Err(e) = self.history.forward() {
+            error!("failed to go forward: ", e)
+        }
+    }
+
+    fn push(&mut self, state: R) {
+        let path = self.full_path(&state);
+
+        let state: [f64; 2] = self.create_state(state);
+        self.handle_nav(push_state_and_url(&self.history, &state, path));
+    }
+
     fn replace(&mut self, state: R) {
         let path = match &self.prefix {
             None => format!("{state}"),
@@ -180,27 +354,11 @@ where
         };
 
         let state = self.create_state(state);
-
-        let nav = replace_state_with_url(&self.history, &state, Some(&path));
-
-        match nav {
-            Ok(_) => {
-                if self.do_scroll_restoration {
-                    self.window.scroll_to_with_x_and_y(0.0, 0.0)
-                }
-            }
-            Err(e) => error!("failed to replace state:", e),
-        }
+        self.handle_nav(replace_state_with_url(&self.history, &state, Some(&path)));
     }
 
     fn external(&mut self, url: String) -> bool {
-        match self.window.location().set_href(&url) {
-            Ok(_) => true,
-            Err(e) => {
-                error!("failed to navigate to external url (", url, "): ", e);
-                false
-            }
-        }
+        self.navigate_external(url)
     }
 
     fn updater(&mut self, callback: std::sync::Arc<dyn Fn() + Send + Sync>) {
@@ -213,8 +371,8 @@ where
             (*callback)();
             if d {
                 let mut s = s.lock().expect("unpoisoned scroll mutex");
-                if let Some(current_state) = get_current::<WebHistoryState<R>>(&h) {
-                    *s = Some(current_state.scroll.scroll_to(w.clone()));
+                if let Some([x, y]) = get_current(&h) {
+                    *s = Some(ScrollPosition { x, y }.scroll_to(w.clone()));
                 }
             }
         }));

+ 49 - 4
packages/router/src/history/web_history.rs

@@ -1,10 +1,24 @@
 use gloo::console::error;
+#[cfg(feature = "serde")]
 use gloo_utils::format::JsValueSerdeExt;
-use serde::{de::DeserializeOwned, Serialize};
 use wasm_bindgen::JsValue;
 use web_sys::History;
 
-pub(crate) fn replace_state_with_url<V: Serialize>(
+#[cfg(not(feature = "serde"))]
+pub(crate) fn replace_state_with_url(
+    history: &History,
+    value: &[f64; 2],
+    url: Option<&str>,
+) -> Result<(), JsValue> {
+    let position = js_sys::Array::new();
+    position.push(&JsValue::from(value[0]));
+    position.push(&JsValue::from(value[1]));
+
+    history.replace_state_with_url(&position, "", url)
+}
+
+#[cfg(feature = "serde")]
+pub(crate) fn replace_state_with_url<V: serde::Serialize>(
     history: &History,
     value: &V,
     url: Option<&str>,
@@ -14,7 +28,21 @@ pub(crate) fn replace_state_with_url<V: Serialize>(
     history.replace_state_with_url(&position, "", url)
 }
 
-pub(crate) fn push_state_and_url<V: Serialize>(
+#[cfg(not(feature = "serde"))]
+pub(crate) fn push_state_and_url(
+    history: &History,
+    value: &[f64; 2],
+    url: String,
+) -> Result<(), JsValue> {
+    let position = js_sys::Array::new();
+    position.push(&JsValue::from(value[0]));
+    position.push(&JsValue::from(value[1]));
+
+    history.push_state_with_url(&position, "", Some(&url))
+}
+
+#[cfg(feature = "serde")]
+pub(crate) fn push_state_and_url<V: serde::Serialize>(
     history: &History,
     value: &V,
     url: String,
@@ -24,7 +52,8 @@ pub(crate) fn push_state_and_url<V: Serialize>(
     history.push_state_with_url(&position, "", Some(&url))
 }
 
-pub(crate) fn get_current<V: DeserializeOwned>(history: &History) -> Option<V> {
+#[cfg(feature = "serde")]
+pub(crate) fn get_current<V: serde::de::DeserializeOwned>(history: &History) -> Option<V> {
     let state = history.state();
     if let Err(err) = &state {
         error!(err);
@@ -37,3 +66,19 @@ pub(crate) fn get_current<V: DeserializeOwned>(history: &History) -> Option<V> {
         deserialized.ok()
     })
 }
+
+#[cfg(not(feature = "serde"))]
+pub(crate) fn get_current(history: &History) -> Option<[f64; 2]> {
+    use wasm_bindgen::JsCast;
+
+    let state = history.state();
+    if let Err(err) = &state {
+        error!(err);
+    }
+    state.ok().and_then(|state| {
+        let state = state.dyn_into::<js_sys::Array>().ok()?;
+        let x = state.get(0).as_f64()?;
+        let y = state.get(1).as_f64()?;
+        Some([x, y])
+    })
+}

+ 4 - 4
packages/router/src/history/web_scroll.rs

@@ -1,11 +1,11 @@
 use gloo::render::{request_animation_frame, AnimationFrame};
-use serde::{Deserialize, Serialize};
 use web_sys::Window;
 
-#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
+#[derive(Clone, Copy, Debug, Default)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
 pub(crate) struct ScrollPosition {
-    x: f64,
-    y: f64,
+    pub x: f64,
+    pub y: f64,
 }
 
 impl ScrollPosition {

+ 1 - 2
packages/router/src/router_cfg.rs

@@ -2,7 +2,6 @@ use crate::contexts::router::RoutingCallback;
 use crate::history::HistoryProvider;
 use crate::routable::Routable;
 use dioxus::prelude::*;
-use serde::{de::DeserializeOwned, Serialize};
 
 use crate::prelude::*;
 
@@ -52,7 +51,7 @@ pub struct RouterConfiguration<R: Routable> {
     pub on_update: Option<RoutingCallback<R>>,
 }
 
-impl<R: Routable + Clone + Serialize + DeserializeOwned> Default for RouterConfiguration<R>
+impl<R: Routable + Clone> Default for RouterConfiguration<R>
 where
     <R as std::str::FromStr>::Err: std::fmt::Display,
 {