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 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.
72extern crate self as sycamore;
73
74#[doc(hidden)]
75pub mod rt {
76 pub use sycamore_core::*;
77 #[cfg(feature = "suspense")]
78 pub use sycamore_futures::*;
79 pub use sycamore_macro::*;
80 pub use sycamore_reactive::*;
81 #[allow(unused_imports)] // Needed for macro support.
82 pub use web_sys;
83
84 #[cfg(feature = "suspense")]
85 pub use crate::WrapAsync;
86 pub use crate::{bind, custom_element, tags, View};
87}
88
89/// Re-export of `js-sys` and `wasm-bindgen` for convenience.
90//#[doc(no_inline)]
91pub use {js_sys, wasm_bindgen};
92
93/// A macro that expands to whether we are in SSR mode or not.
94///
95/// Can also be used with a block to only include the code inside the block if in SSR mode.
96///
97/// # Example
98/// ```
99/// # use sycamore_web::*;
100/// # fn access_database() {}
101/// if is_ssr!() {
102/// println!("We are running on the server!");
103/// }
104///
105/// is_ssr! {
106/// // Access server only APIs in here.
107/// let _ = access_database();
108/// }
109/// ```
110#[macro_export]
111macro_rules! is_ssr {
112 () => {
113 cfg!(any(not(target_arch = "wasm32"), sycamore_force_ssr))
114 };
115 ($($tt:tt)*) => {
116 #[cfg(any(not(target_arch = "wasm32"), sycamore_force_ssr))]
117 { $($tt)* }
118 };
119}
120
121/// A macro that expands to whether we are in DOM mode or not.
122///
123/// Can also be used with a block to only include the code inside the block if in DOM mode.
124///
125/// # Example
126/// ```
127/// # use sycamore_web::*;
128/// if is_not_ssr!() {
129/// console_log!("We are running in the browser!");
130/// }
131///
132/// is_not_ssr! {
133/// // Access browser only APIs in here.
134/// let document = document();
135/// }
136/// ```
137#[macro_export]
138macro_rules! is_not_ssr {
139 () => {
140 !$crate::is_ssr!()
141 };
142 ($($tt:tt)*) => {
143 #[cfg(all(target_arch = "wasm32", not(sycamore_force_ssr)))]
144 { $($tt)* }
145 };
146}
147
148/// `macro_rules!` equivalent of [`cfg_ssr`]. This is to get around the limitation of not being
149/// able to put proc-macros on `mod` items.
150#[macro_export]
151macro_rules! cfg_ssr_item {
152 ($item:item) => {
153 #[cfg(any(not(target_arch = "wasm32"), sycamore_force_ssr))]
154 $item
155 };
156}
157
158/// `macro_rules!` equivalent of [`cfg_not_ssr`]. This is to get around the limitation of not being
159/// able to put proc-macros on `mod` items.
160#[macro_export]
161macro_rules! cfg_not_ssr_item {
162 ($item:item) => {
163 #[cfg(all(target_arch = "wasm32", not(sycamore_force_ssr)))]
164 $item
165 };
166}
167
168/// A type alias for the rendering backend.
169#[cfg_ssr]
170pub type HtmlNode = SsrNode;
171/// A type alias for the rendering backend.
172#[cfg_not_ssr]
173#[cfg(not(feature = "hydrate"))]
174pub type HtmlNode = DomNode;
175/// A type alias for the rendering backend.
176#[cfg_not_ssr]
177#[cfg(feature = "hydrate")]
178pub type HtmlNode = HydrateNode;
179
180/// A type alias for [`Children`](sycamore_core::Children) automatically selecting the correct node
181/// type.
182pub type Children = sycamore_core::Children<View>;
183
184/// Create a new effect, but only if we are not in SSR mode.
185pub fn create_client_effect(f: impl FnMut() + 'static) {
186 if is_not_ssr!() {
187 create_effect(f);
188 }
189}
190
191/// Queue up a callback to be executed when the component is mounted.
192///
193/// If not on `wasm32` target, does nothing.
194///
195/// # Potential Pitfalls
196///
197/// If called inside an async-component, the callback will be called after the next suspension
198/// point (when there is an `.await`).
199pub fn on_mount(f: impl FnOnce() + 'static) {
200 if cfg!(target_arch = "wasm32") {
201 let is_alive = Rc::new(Cell::new(true));
202 on_cleanup({
203 let is_alive = Rc::clone(&is_alive);
204 move || is_alive.set(false)
205 });
206
207 let scope = use_current_scope();
208 let cb = move || {
209 if is_alive.get() {
210 scope.run_in(f);
211 }
212 };
213 queue_microtask(cb);
214 }
215}
216
217/// Alias for `queueMicrotask`.
218pub fn queue_microtask(f: impl FnOnce() + 'static) {
219 #[wasm_bindgen]
220 extern "C" {
221 #[wasm_bindgen(js_name = "queueMicrotask")]
222 fn queue_microtask_js(f: &wasm_bindgen::JsValue);
223 }
224 queue_microtask_js(&Closure::once_into_js(f));
225}
226
227/// Utility function for accessing the global [`web_sys::Window`] object.
228pub fn window() -> web_sys::Window {
229 web_sys::window().expect("no global `window` exists")
230}
231
232/// Utility function for accessing the global [`web_sys::Document`] object.
233pub fn document() -> web_sys::Document {
234 thread_local! {
235 /// Cache for small performance improvement by preventing repeated calls to `window().document()`.
236 static DOCUMENT: web_sys::Document = window().document().expect("no `document` exists");
237 }
238 DOCUMENT.with(Clone::clone)
239}