sycamore_web/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
//! # `sycamore-web`
//!
//! Web rendering backend for [`sycamore`](https://docs.rs/sycamore). This is already re-exported
//! in the main `sycamore` crate, so you should rarely need to use this crate directly.
//!
//! ## Feature flags
//!
//! - `hydrate` - Enables hydration support in DOM node. By default, hydration is disabled to reduce
//!   binary size.
//!
//! - `suspense` - Enables suspense and resources support.
//!
//! - `wasm-bindgen-interning` (_default_) - Enables interning for `wasm-bindgen` strings. This
//!   improves performance at a slight cost in binary size. If you want to minimize the size of the
//!   resulting `.wasm` binary, you might want to disable this.
//!
//! ## Server Side Rendering
//!
//! This crate uses target detection to determine whether to use DOM or SSR as the rendering
//! backend. If the target arch is `wasm32`, DOM rendering will be used. Otherwise, SSR will be
//! used. Sometimes, this isn't desirable (e.g., if using server side wasm). To override this
//! behavior, you can set `--cfg sycamore_force_ssr` in your `RUSTFLAGS` environment variable when
//! compiling to force SSR mode even on `wasm32`.

// NOTE: Determining whether we are in SSR mode or not uses the cfg_ssr! and cfg_not_ssr! macros.
// For dependencies, we have to put in the conditions manually.

use std::borrow::Cow;
use std::cell::Cell;
use std::rc::Rc;

use sycamore_macro::*;
use sycamore_reactive::*;
use wasm_bindgen::prelude::*;

pub mod bind;
pub mod events;
#[doc(hidden)]
pub mod utils;

mod attributes;
mod components;
mod elements;
mod iter;
mod macros;
mod node;
mod noderef;
mod portal;
#[cfg(feature = "suspense")]
mod resource;
mod stable_counter;
#[cfg(feature = "suspense")]
mod suspense;

pub(crate) mod view;

pub use self::attributes::*;
pub use self::components::*;
pub use self::elements::*;
pub use self::iter::*;
pub use self::node::*;
pub use self::noderef::*;
pub use self::portal::*;
#[cfg(feature = "suspense")]
pub use self::resource::*;
pub use self::stable_counter::*;
#[cfg(feature = "suspense")]
pub use self::suspense::*;
pub use self::view::*;

/// We add this to make the macros from `sycamore-macro` work properly.
extern crate self as sycamore;

#[doc(hidden)]
pub mod rt {
    pub use sycamore_core::*;
    #[cfg(feature = "suspense")]
    pub use sycamore_futures::*;
    pub use sycamore_macro::*;
    pub use sycamore_reactive::*;
    #[allow(unused_imports)] // Needed for macro support.
    pub use web_sys;

    #[cfg(feature = "suspense")]
    pub use crate::WrapAsync;
    pub use crate::{bind, custom_element, tags, View};
}

/// Re-export of `js-sys` and `wasm-bindgen` for convenience.
//#[doc(no_inline)]
pub use {js_sys, wasm_bindgen};

/// A macro that expands to whether we are in SSR mode or not.
///
/// Can also be used with a block to only include the code inside the block if in SSR mode.
///
/// # Example
/// ```
/// # use sycamore_web::*;
/// # fn access_database() {}
/// if is_ssr!() {
///     println!("We are running on the server!");
/// }
///
/// is_ssr! {
///     // Access server only APIs in here.
///     let _ = access_database();
/// }
/// ```
#[macro_export]
macro_rules! is_ssr {
    () => {
        cfg!(any(not(target_arch = "wasm32"), sycamore_force_ssr))
    };
    ($($tt:tt)*) => {
        #[cfg(any(not(target_arch = "wasm32"), sycamore_force_ssr))]
        { $($tt)* }
    };
}

/// A macro that expands to whether we are in DOM mode or not.
///
/// Can also be used with a block to only include the code inside the block if in DOM mode.
///
/// # Example
/// ```
/// # use sycamore_web::*;
/// if is_not_ssr!() {
///     console_log!("We are running in the browser!");
/// }
///
/// is_not_ssr! {
///     // Access browser only APIs in here.
///     let document = document();
/// }
/// ```
#[macro_export]
macro_rules! is_not_ssr {
    () => {
        !$crate::is_ssr!()
    };
    ($($tt:tt)*) => {
        #[cfg(all(target_arch = "wasm32", not(sycamore_force_ssr)))]
        { $($tt)* }
    };
}

/// `macro_rules!` equivalent of [`cfg_ssr`]. This is to get around the limitation of not being
/// able to put proc-macros on `mod` items.
#[macro_export]
macro_rules! cfg_ssr_item {
    ($item:item) => {
        #[cfg(any(not(target_arch = "wasm32"), sycamore_force_ssr))]
        $item
    };
}

/// `macro_rules!` equivalent of [`cfg_not_ssr`]. This is to get around the limitation of not being
/// able to put proc-macros on `mod` items.
#[macro_export]
macro_rules! cfg_not_ssr_item {
    ($item:item) => {
        #[cfg(all(target_arch = "wasm32", not(sycamore_force_ssr)))]
        $item
    };
}

/// A type alias for the rendering backend.
#[cfg_ssr]
pub type HtmlNode = SsrNode;
/// A type alias for the rendering backend.
#[cfg_not_ssr]
#[cfg(not(feature = "hydrate"))]
pub type HtmlNode = DomNode;
/// A type alias for the rendering backend.
#[cfg_not_ssr]
#[cfg(feature = "hydrate")]
pub type HtmlNode = HydrateNode;

/// A type alias for [`Children`](sycamore_core::Children) automatically selecting the correct node
/// type.
pub type Children = sycamore_core::Children<View>;

/// Create a new effect, but only if we are not in SSR mode.
pub fn create_client_effect(f: impl FnMut() + 'static) {
    if is_not_ssr!() {
        create_effect(f);
    }
}

/// Queue up a callback to be executed when the component is mounted.
///
/// If not on `wasm32` target, does nothing.
///
/// # Potential Pitfalls
///
/// If called inside an async-component, the callback will be called after the next suspension
/// point (when there is an `.await`).
pub fn on_mount(f: impl FnOnce() + 'static) {
    if cfg!(target_arch = "wasm32") {
        let is_alive = Rc::new(Cell::new(true));
        on_cleanup({
            let is_alive = Rc::clone(&is_alive);
            move || is_alive.set(false)
        });

        let scope = use_current_scope();
        let cb = move || {
            if is_alive.get() {
                scope.run_in(f);
            }
        };
        queue_microtask(cb);
    }
}

/// Alias for `queueMicrotask`.
pub fn queue_microtask(f: impl FnOnce() + 'static) {
    #[wasm_bindgen]
    extern "C" {
        #[wasm_bindgen(js_name = "queueMicrotask")]
        fn queue_microtask_js(f: &wasm_bindgen::JsValue);
    }
    queue_microtask_js(&Closure::once_into_js(f));
}

/// Utility function for accessing the global [`web_sys::Window`] object.
pub fn window() -> web_sys::Window {
    web_sys::window().expect("no global `window` exists")
}

/// Utility function for accessing the global [`web_sys::Document`] object.
pub fn document() -> web_sys::Document {
    thread_local! {
        /// Cache for small performance improvement by preventing repeated calls to `window().document()`.
        static DOCUMENT: web_sys::Document = window().document().expect("no `document` exists");
    }
    DOCUMENT.with(Clone::clone)
}