Skip to main content
This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew_router/
utils.rs

1use std::cell::RefCell;
2
3use wasm_bindgen::JsCast;
4
5pub(crate) fn strip_slash_suffix(path: &str) -> &str {
6    path.strip_suffix('/').unwrap_or(path)
7}
8
9static BASE_URL_LOADED: std::sync::Once = std::sync::Once::new();
10thread_local! {
11    static BASE_URL: RefCell<Option<String>> = const { RefCell::new(None) };
12}
13
14/// Returns a cached value of [`fetch_base_url`]. The cache is never invalidated;
15/// prefer [`fetch_base_url`] for a live read.
16#[doc(hidden)]
17pub fn base_url() -> Option<String> {
18    BASE_URL_LOADED.call_once(|| {
19        BASE_URL.with(|val| {
20            *val.borrow_mut() = fetch_base_url();
21        })
22    });
23    BASE_URL.with(|it| it.borrow().as_ref().map(|it| it.to_string()))
24}
25
26pub fn fetch_base_url() -> Option<String> {
27    match gloo::utils::document().query_selector("base[href]") {
28        Ok(Some(base)) => {
29            let base = base.unchecked_into::<web_sys::HtmlBaseElement>().href();
30
31            let url = web_sys::Url::new(&base).unwrap();
32            let base = url.pathname();
33
34            let base = if base != "/" {
35                strip_slash_suffix(&base)
36            } else {
37                return None;
38            };
39
40            Some(base.to_string())
41        }
42        _ => None,
43    }
44}
45
46#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
47pub fn compose_path(pathname: &str, query: &str) -> Option<String> {
48    gloo::utils::window()
49        .location()
50        .href()
51        .ok()
52        .and_then(|base| web_sys::Url::new_with_base(pathname, &base).ok())
53        .map(|url| {
54            url.set_search(query);
55            format!("{}{}", url.pathname(), url.search())
56        })
57}
58
59#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
60pub fn compose_path(pathname: &str, query: &str) -> Option<String> {
61    let query = query.trim();
62
63    if !query.is_empty() {
64        Some(format!("{pathname}?{query}"))
65    } else {
66        Some(pathname.to_owned())
67    }
68}
69
70// TODO: remove the cfg after wasm-bindgen-test stops emitting the function unconditionally
71#[cfg(all(
72    test,
73    target_arch = "wasm32",
74    any(target_os = "unknown", target_os = "none")
75))]
76mod tests {
77    use gloo::utils::document;
78    use wasm_bindgen_test::wasm_bindgen_test as test;
79    use yew_router::prelude::*;
80    use yew_router::utils::*;
81
82    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
83
84    #[derive(Debug, Clone, Copy, PartialEq, Routable)]
85    enum Routes {
86        #[at("/")]
87        Home,
88        #[at("/no")]
89        No,
90        #[at("/404")]
91        NotFound,
92    }
93
94    #[test]
95    fn test_base_url() {
96        document().head().unwrap().set_inner_html(r#""#);
97
98        assert_eq!(fetch_base_url(), None);
99
100        document()
101            .head()
102            .unwrap()
103            .set_inner_html(r#"<base href="/base/">"#);
104        assert_eq!(fetch_base_url(), Some("/base".to_string()));
105
106        document()
107            .head()
108            .unwrap()
109            .set_inner_html(r#"<base href="/base">"#);
110        assert_eq!(fetch_base_url(), Some("/base".to_string()));
111    }
112
113    #[test]
114    fn test_compose_path() {
115        assert_eq!(compose_path("/home", ""), Some("/home".to_string()));
116        assert_eq!(
117            compose_path("/path/to", "foo=bar"),
118            Some("/path/to?foo=bar".to_string())
119        );
120        assert_eq!(
121            compose_path("/events", "from=2019&to=2021"),
122            Some("/events?from=2019&to=2021".to_string())
123        );
124    }
125}