sycamore_web/
lib.rs

1//! # `sycamore-web`
2//!
3//! Web rendering backend for [`sycamore`](https://docs.rs/sycamore). This is already re-exported
4//! in the main `sycamore` crate, so you should rarely need to use this crate directly.
5//!
6//! ## Feature flags
7//!
8//! - `hydrate` - Enables hydration support in DOM node. By default, hydration is disabled to reduce
9//!   binary size.
10//!
11//! - `suspense` - Enables suspense and resources support.
12//!
13//! - `wasm-bindgen-interning` (_default_) - Enables interning for `wasm-bindgen` strings. This
14//!   improves performance at a slight cost in binary size. If you want to minimize the size of the
15//!   resulting `.wasm` binary, you might want to disable this.
16//!
17//! ## Server Side Rendering
18//!
19//! This crate uses target detection to determine whether to use DOM or SSR as the rendering
20//! backend. If the target arch is `wasm32`, DOM rendering will be used. Otherwise, SSR will be
21//! used. Sometimes, this isn't desirable (e.g., if using server side wasm). To override this
22//! behavior, you can set `--cfg sycamore_force_ssr` in your `RUSTFLAGS` environment variable when
23//! compiling to force SSR mode even on `wasm32`.
24
25// NOTE: Determining whether we are in SSR mode or not uses the cfg_ssr! and cfg_not_ssr! macros.
26// For dependencies, we have to put in the conditions manually.
27
28use std::borrow::Cow;
29use std::cell::Cell;
30use std::rc::Rc;
31
32use sycamore_macro::*;
33use sycamore_reactive::*;
34use wasm_bindgen::prelude::*;
35
36pub mod bind;
37pub mod events;
38#[doc(hidden)]
39pub mod utils;
40
41mod attributes;
42mod components;
43mod elements;
44mod iter;
45mod macros;
46mod node;
47mod noderef;
48mod portal;
49#[cfg(feature = "suspense")]
50mod resource;
51mod stable_counter;
52#[cfg(feature = "suspense")]
53mod suspense;
54
55pub(crate) mod view;
56
57pub use self::attributes::*;
58pub use self::components::*;
59pub use self::elements::*;
60pub use self::iter::*;
61pub use self::node::*;
62pub use self::noderef::*;
63pub use self::portal::*;
64#[cfg(feature = "suspense")]
65pub use self::resource::*;
66pub use self::stable_counter::*;
67#[cfg(feature = "suspense")]
68pub use self::suspense::*;
69pub use self::view::*;
70
71/// We add this to make the macros from `sycamore-macro` work properly.
72/// We also add the `web` module so that the macros can access `::sycamore::web` correctly.
73extern crate self as sycamore;
74mod web {
75    pub use crate::*;
76}
77
78#[doc(hidden)]
79pub mod rt {
80    pub use sycamore_core::*;
81    #[cfg(feature = "suspense")]
82    pub use sycamore_futures::*;
83    pub use sycamore_macro::*;
84    pub use sycamore_reactive::*;
85    #[allow(unused_imports)] // Needed for macro support.
86    pub use web_sys;
87
88    #[cfg(feature = "suspense")]
89    pub use crate::WrapAsync;
90    pub use crate::{bind, custom_element, tags, View};
91}
92
93/// Re-export of `js-sys` and `wasm-bindgen` for convenience.
94//#[doc(no_inline)]
95pub use {js_sys, wasm_bindgen};
96
97// Internal implementation for `cfg_ssr` and `cfg_not_ssr` macros.
98#[cfg(any(not(target_arch = "wasm32"), sycamore_force_ssr))]
99#[macro_export]
100#[doc(hidden)]
101macro_rules! _is_ssr {
102    () => { true };
103    ($($tt:tt)*) => {
104        $($tt)*
105    };
106}
107#[cfg(all(target_arch = "wasm32", not(sycamore_force_ssr)))]
108#[macro_export]
109#[doc(hidden)]
110macro_rules! _is_ssr {
111    () => {
112        false
113    };
114    ($($tt:tt)*) => {
115        // This is a no-op in DOM mode.
116    };
117}
118#[cfg(any(not(target_arch = "wasm32"), sycamore_force_ssr))]
119#[macro_export]
120#[doc(hidden)]
121macro_rules! _is_not_ssr {
122    () => {
123        false
124    };
125    ($($tt:tt)*) => {
126        // This is a no-op in SSR mode.
127    };
128}
129#[cfg(all(target_arch = "wasm32", not(sycamore_force_ssr)))]
130#[macro_export]
131#[doc(hidden)]
132macro_rules! _is_not_ssr {
133    () => { true };
134    ($($tt:tt)*) => {
135        $($tt)*
136    };
137}
138
139/// A macro that expands to whether we are in SSR mode or not.
140///
141/// Can also be used with a block to only include the code inside the block if in SSR mode.
142///
143/// # Example
144/// ```
145/// # use sycamore_web::*;
146/// # fn access_database() {}
147/// if is_ssr!() {
148///     println!("We are running on the server!");
149/// }
150///
151/// is_ssr! {
152///     // Access server only APIs in here.
153///     let _ = access_database();
154/// }
155/// ```
156#[macro_export]
157macro_rules! is_ssr {
158    ($($tt:tt)*) => {
159        $crate::_is_ssr! { $($tt)* }
160    };
161}
162
163/// A macro that expands to whether we are in DOM mode or not.
164///
165/// Can also be used with a block to only include the code inside the block if in DOM mode.
166///
167/// # Example
168/// ```
169/// # use sycamore_web::*;
170/// if is_not_ssr!() {
171///     console_log!("We are running in the browser!");
172/// }
173///
174/// is_not_ssr! {
175///     // Access browser only APIs in here.
176///     let document = document();
177/// }
178/// ```
179#[macro_export]
180macro_rules! is_not_ssr {
181    ($($tt:tt)*) => {
182        $crate::_is_not_ssr! { $($tt)* }
183    };
184}
185
186/// `macro_rules!` equivalent of [`cfg_ssr`]. This is to get around the limitation of not being
187/// able to put proc-macros on `mod` items.
188#[deprecated(since = "0.9.2", note = "use `is_ssr!` instead")]
189#[macro_export]
190macro_rules! cfg_ssr_item {
191    ($item:item) => {
192        $crate::is_ssr! { $item }
193    };
194}
195
196/// `macro_rules!` equivalent of [`cfg_not_ssr`]. This is to get around the limitation of not being
197/// able to put proc-macros on `mod` items.
198#[deprecated(since = "0.9.2", note = "use `is_not_ssr!` instead")]
199#[macro_export]
200macro_rules! cfg_not_ssr_item {
201    ($item:item) => {
202        $crate::is_not_ssr! { $item }
203    };
204}
205
206/// A type alias for the rendering backend.
207#[cfg_ssr]
208pub type HtmlNode = SsrNode;
209/// A type alias for the rendering backend.
210#[cfg_not_ssr]
211#[cfg(not(feature = "hydrate"))]
212pub type HtmlNode = DomNode;
213/// A type alias for the rendering backend.
214#[cfg_not_ssr]
215#[cfg(feature = "hydrate")]
216pub type HtmlNode = HydrateNode;
217
218/// A type alias for [`Children`](sycamore_core::Children) automatically selecting the correct node
219/// type.
220pub type Children = sycamore_core::Children<View>;
221
222/// Create a new effect, but only if we are not in SSR mode.
223pub fn create_client_effect(f: impl FnMut() + 'static) {
224    if is_not_ssr!() {
225        create_effect(f);
226    }
227}
228
229/// Queue up a callback to be executed when the component is mounted.
230///
231/// If not on `wasm32` target, does nothing.
232///
233/// # Potential Pitfalls
234///
235/// If called inside an async-component, the callback will be called after the next suspension
236/// point (when there is an `.await`).
237pub fn on_mount(f: impl FnOnce() + 'static) {
238    if cfg!(target_arch = "wasm32") {
239        let is_alive = Rc::new(Cell::new(true));
240        on_cleanup({
241            let is_alive = Rc::clone(&is_alive);
242            move || is_alive.set(false)
243        });
244
245        let scope = use_current_scope();
246        let cb = move || {
247            if is_alive.get() {
248                scope.run_in(f);
249            }
250        };
251        queue_microtask(cb);
252    }
253}
254
255/// Alias for `queueMicrotask`.
256pub fn queue_microtask(f: impl FnOnce() + 'static) {
257    #[wasm_bindgen]
258    extern "C" {
259        #[wasm_bindgen(js_name = "queueMicrotask")]
260        fn queue_microtask_js(f: &wasm_bindgen::JsValue);
261    }
262    queue_microtask_js(&Closure::once_into_js(f));
263}
264
265/// Utility function for accessing the global [`web_sys::Window`] object.
266pub fn window() -> web_sys::Window {
267    web_sys::window().expect("no global `window` exists")
268}
269
270/// Utility function for accessing the global [`web_sys::Document`] object.
271pub fn document() -> web_sys::Document {
272    thread_local! {
273        /// Cache for small performance improvement by preventing repeated calls to `window().document()`.
274        static DOCUMENT: web_sys::Document = window().document().expect("no `document` exists");
275    }
276    DOCUMENT.with(Clone::clone)
277}