silkenweb/lib.rs
1//! A library for building reactive web apps
2//!
3//! # Overview
4//!
5//! - Pure rust API
6//! - Fine grained reactivity using [`futures_signals`]
7//! - [Routing]
8//! - [Tauri] support
9//! - [Server Side Rendering] with hydration
10//!
11//! # Book
12//!
13//! The Silkenweb [book] provides a high level introduction to Silkenweb's main
14//! concepts.
15//!
16//! # Quick Start
17//!
18//! First, install the `wasm32` target:
19//!
20//! ```bash
21//! rustup target add wasm32-unknown-unknown
22//! ```
23//!
24//! Then install [trunk]:
25//!
26//! ```bash
27//! cargo install trunk --locked
28//! ```
29//!
30//! To run the example [counter]:
31//!
32//! ```bash
33//! cd examples/counter
34//! trunk serve --open
35//! ```
36//!
37//! # Feature Flags
38//!
39//! ## `weak-refs`
40//!
41//! Use Javascript weak references to manage event callbacks. This improves
42//! performance but must be enabled in `wasm-bindgen`. See the [trunk]
43//! documentation for details on how to do this using `data-weak-refs`.
44//!
45//! See [caniuse](https://caniuse.com/mdn-javascript_builtins_weakref) for
46//! current browser support.
47//!
48//! ## `declarative-shadow-dom`
49//!
50//! Print [Declarative Shadow DOM] when server side rendering. Hydration will
51//! correctly deal with shadow DOM regardless of this flag. See
52//! [caniuse](https://caniuse.com/mdn-html_elements_template_shadowroot)
53//! for browser support. Polyfills are available.
54//!
55//! ## `css-transpile`
56//!
57//! Enable CSS transpilation for [`css!`]. This can significantly increase build
58//! time, so is presented as an opt-in feature.
59//!
60//! # Learning
61//!
62//! There's extensive documentation on each module in this crate, along with
63//! many other examples in the [examples] folder.
64//!
65//! Reactivity is provided by [`futures_signals`]. It would be helpful to
66//! familiarize yourself using [`futures_signals::tutorial`].
67//!
68//! [book]: https://silkenweb.netlify.app/book/
69//! [trunk]: https://trunkrs.dev/
70//! [counter]: https://github.com/silkenweb/silkenweb/tree/main/examples/counter
71//! [routing]: https://github.com/silkenweb/silkenweb/tree/main/examples/router
72//! [tauri]: https://github.com/silkenweb/silkenweb/tree/main/examples/tauri
73//! [Server Side Rendering]: https://github.com/silkenweb/silkenweb/tree/main/examples/ssr-full
74//! [examples]: https://github.com/silkenweb/silkenweb/tree/main/examples
75//! [Declarative Shadow DOM]: https://web.dev/declarative-shadow-dom/
76
77#[doc(inline)]
78pub use clonelet::clone;
79use document::Document;
80use dom::DefaultDom;
81use node::element::{Const, GenericElement};
82use silkenweb_base::document as base_document;
83/// Define `&str` constants for each class in a CSS file.
84///
85/// This defines 2 modules:
86///
87/// - `mod class` with constants or functions (depending on `auto_mount`) for
88/// each CSS class. For a CSS class called `my-css-class`, a constant called
89/// `MY_CSS_CLASS` or a function called `my_css_class` will be defined.
90/// - `mod stylesheet` with:
91/// - An `fn text() -> &'static str` that gets the content of the
92/// stylesheet.
93/// - `fn mount()` and `fn mount_dom<D: Document>()` that lazily call
94/// [`DefaultDom::mount_in_head`] or `D:mount_in_head` once, respectively.
95/// This ensures the stylesheet is in the head. Any subsequent calls to
96/// either function will have no effect.
97///
98/// The macro takes two forms. Firstly it can take a single string literal which
99/// is the path to the CSS/SCSS/SASS file. The path is relative to the
100/// `$CARGO_MANIFEST_DIR` environment variable.
101///
102/// Alternatively, named parameters can be specified.
103///
104/// # Parameters
105///
106/// Parameters take the form:
107///
108/// ```
109/// # use silkenweb_macros::css;
110/// css!(
111/// path = "my-css-file.css",
112/// syntax = "css",
113/// prefix = "prefix",
114/// include_prefixes = ["included-"],
115/// exclude_prefixes = ["excluded-"],
116/// auto_mount,
117#[cfg_attr(
118 feature = "css-transpile",
119 doc = r#"
120 validate,
121 transpile = (
122 minify,
123 pretty,
124 modules,
125 nesting,
126 browsers = (
127 android = (1, 0, 0),
128 chrome = (1, 0, 0),
129 edge = (1, 0, 0),
130 firefox = (1, 0, 0),
131 ie = (1, 0, 0),
132 ios_saf = (1, 0, 0),
133 opera = (1, 0, 0),
134 safari = (1, 0, 0),
135 samsung = (1, 0, 0),
136 )
137 )
138"#
139)]
140/// );
141/// ```
142///
143/// All are optional, but one of `path` or `content` must be specified.
144///
145/// - `path` is the path to the CSS/SCSS/SASS file. The syntax is determined
146/// from the extension.
147/// - `syntax` explicitly specifies the syntax. It must be one of "css", "scss",
148/// or "sass". The default is "css". It overrides any syntax inferred from
149/// `path`.
150/// - `content` is the css content.
151/// - `prefix`: only classes starting with `prefix` should be included. Their
152/// Rust names will have the prefix stripped.
153/// - `include_prefixes`: a list of prefixes to include, without stripping the
154/// prefix. Rust constants will only be defined for classes starting with one
155/// or more of these prefixes.
156/// - `exclude_prefixes`: a list of prefixes to exclude. No Rust constants will
157/// be defined for a class starting with any of these prefixes.
158/// `exclude_prefixes` takes precedence over `include_prefixes`.
159/// - `auto_mount`: Generate a function for each CSS class that will call
160/// `stylesheet::mount` before returning the class name.
161/// - `validate`: validate the CSS. Requires crate feature `css-transpile`.
162/// - `transpile`: transpile the CSS with [lightningcss]. Requires crate feature `css-transpile`.
163///
164/// ## `transpile`
165///
166/// - `minify`: Minify the CSS returned by `stylesheet()`. Minification also
167/// adds/removes vendor prefixes, so it's a good idea to keep this the same
168/// between debug and release builds. Use `pretty` if you want legible CSS in
169/// debug.
170/// - `pretty`: Pretty print the final output. This is the default unless minify
171/// is specified.
172/// - `modules`: Enable [CSS Modules] to locally scope class identifiers, via
173/// [lightningcss]. Composition is unsupported.
174/// - `nesting`: Allow CSS nesting.
175/// - `browsers` is a comma seperated list of the minimum supported browser
176/// versions. This will add vendor prefixes to the CSS from `stylesheet()`.
177/// The version is a paranthesized `,` seperated string of major, minor, and
178/// patch versions. For example, to support firefox 110 + and chrome 111+,
179/// use `browsers =( firefox = (110, 0, 0), chrome = (111, 0, 0) )`.
180///
181/// # Examples
182///
183/// Define private constants for all CSS classes:
184/// ```
185/// # use silkenweb_macros::css;
186/// css!("my-css-file.css");
187/// assert_eq!(class::MY_CLASS, "my-class");
188/// ```
189///
190/// Define private constants for all content CSS classes:
191///
192/// ```
193/// # use silkenweb_macros::css;
194/// css!(content = r#"
195/// .my-class {
196/// color: hotpink;
197/// }
198/// "#);
199/// assert_eq!(class::MY_CLASS, "my-class");
200/// assert_eq!(stylesheet::text(), r#"
201/// .my-class {
202/// color: hotpink;
203/// }
204/// "#);
205/// ```
206///
207/// Include classes starting with `border-`, except classes starting with
208/// `border-excluded-`:
209/// ```
210/// # use silkenweb_macros::css;
211/// css!(
212/// path = "my-css-file.css",
213/// prefix = "border-",
214/// exclude_prefixes = ["border-excluded-"]
215/// );
216///
217/// assert_eq!(class::SMALL, "border-small");
218/// ```
219///
220/// This won't compile because `exclude_prefixes` takes precedence over
221/// `include_prefixes`:
222/// ```compile_fail
223/// # use silkenweb_macros::css;
224/// css!(
225/// path = "my-css-file.css",
226/// include_prefixes = ["border-"]
227/// exclude_prefixes = ["border-excluded-"]
228/// );
229///
230/// assert_eq!(class::BORDER_EXCLUDED_HUGE, "border-excluded-huge");
231/// ```
232///
233/// [lightningcss]: https://lightningcss.dev/
234/// [`DefaultDom::mount_in_head`]: crate::dom::DefaultDom::mount_in_head
235/// [CSS Modules]: https://github.com/css-modules/css-modules
236pub use silkenweb_macros::css;
237/// Derive the traits needed for a blanket implmenetation of [`ChildElement`].
238///
239/// This only works for structs. It will defer to one field for the
240/// implementation of the traits. If multiple fields are present, a target field
241/// must be specified with `#[child_element(target)]`.
242///
243/// # Example
244///
245/// Derive traits for a newtype struct:
246///
247/// ```
248/// # use silkenweb::{ChildElement, dom::InstantiableDom, node::Component};
249/// #[derive(ChildElement)]
250/// struct MyComponent<D: InstantiableDom>(Component<D>);
251/// ```
252///
253/// Derive traits when the struct has more than 1 field:
254///
255/// ```
256/// # use silkenweb::{ChildElement, dom::InstantiableDom, node::Component};
257/// #[derive(ChildElement)]
258/// struct MyComponent<D: InstantiableDom, Data> {
259/// #[child_element(target)]
260/// component: Component<D>,
261/// data: Data,
262/// }
263/// ```
264///
265/// [`ChildElement`]: crate::node::element::ChildElement
266pub use silkenweb_macros::ChildElement;
267/// Derive [`Element`].
268///
269/// This only works for structs. It will defer to one field for the
270/// implementation. If multiple fields are present, a target field must be
271/// specified with `#[element(target)]`.
272///
273/// # Example
274///
275/// Derive traits for a newtype struct:
276///
277/// ```
278/// # use silkenweb::{dom::Dom, elements::html::Div, Element};
279/// #
280/// #[derive(Element)]
281/// struct MyElement<D: Dom>(Div<D>);
282/// ```
283///
284/// When the struct has more than 1 field:
285///
286/// ```
287/// # use silkenweb::{dom::Dom, elements::html::Div, Element};
288/// #
289/// #[derive(Element)]
290/// struct MyElement<D: Dom, Data> {
291/// #[element(target)]
292/// element: Div<D>,
293/// data: Data,
294/// }
295/// ```
296///
297/// [`Element`]: crate::node::element::Element
298pub use silkenweb_macros::Element;
299/// Derive [`Attribute`] and [`AsAttribute`] for types that implement
300/// [`AsRef<str>`]
301///
302/// # Example
303///
304/// ```
305/// # use silkenweb::{StrAttribute, attribute::{Attribute, AsAttribute}};
306/// use strum::AsRefStr;
307///
308/// #[derive(AsRefStr, StrAttribute)]
309/// enum MyEnum {
310/// A,
311/// B,
312/// }
313/// ```
314///
315/// [`Attribute`]: crate::attribute::Attribute
316/// [`AsAttribute`]: crate::attribute::AsAttribute
317/// [`AsRef<str>`]: ::std::convert::AsRef
318pub use silkenweb_macros::StrAttribute;
319#[doc(inline)]
320pub use silkenweb_macros::{
321 cfg_browser, AriaElement, ElementEvents, HtmlElement, HtmlElementEvents, Value,
322};
323pub use wasm_rs_dbg::dbg;
324
325#[doc(hidden)]
326#[macro_use]
327pub mod macros;
328
329mod event;
330
331pub mod animation;
332pub mod attribute;
333pub mod document;
334pub mod dom;
335pub mod elements;
336pub mod hydration;
337pub mod node;
338pub mod property;
339pub mod router;
340pub mod storage;
341pub mod task;
342pub mod time;
343pub mod window;
344
345pub use futures_signals;
346pub use silkenweb_signals_ext::value;
347
348/// Shorthand for [`DefaultDom::mount`]
349pub fn mount(id: &str, element: impl Into<GenericElement<DefaultDom, Const>>) {
350 #[cfg(debug_assertions)]
351 log_panics();
352 DefaultDom::mount(id, element)
353}
354
355/// Log any `panic!`s to the browser console. This should ideally be the very
356/// first thing that is called, but can be omitted safely. See
357/// [`console_error_panic_hook`] docs for more information.
358pub fn log_panics() {
359 console_error_panic_hook::set_once();
360}
361
362fn mount_point(id: &str) -> web_sys::Element {
363 base_document::get_element_by_id(id)
364 .unwrap_or_else(|| panic!("DOM node id = '{id}' must exist"))
365}
366
367#[cfg_browser(true)]
368pub fn intern_str(s: &str) -> &str {
369 wasm_bindgen::intern(s)
370}
371
372#[cfg_browser(true)]
373pub fn empty_str() -> &'static str {
374 thread_local! {
375 static EMPTY: &'static str = intern_str("");
376 }
377
378 EMPTY.with(|empty| *empty)
379}
380
381#[cfg_browser(false)]
382pub fn intern_str(s: &str) -> &str {
383 s
384}
385
386#[cfg_browser(false)]
387pub fn empty_str() -> &'static str {
388 ""
389}
390
391const HEAD_ID_ATTRIBUTE: &str = "data-silkenweb-head-id";