molt/lib.rs
1//! Fast, type-safe HTML generation for Rust and WebAssembly.
2//!
3//! A composite tree of plain values, escaped by default and rendered compact or pretty,
4//! compiled to native code or to `wasm32-unknown-unknown`.
5//!
6//! The project site, built with Molt itself, is <https://www.molt-framework.io>.
7//!
8//! # Getting started
9//!
10//! ```
11//! use molt::prelude::*;
12//!
13//! let card = article()
14//! .add_class("card p-4")
15//! .child(h1().text("Welcome to Molt!").add_class("text-2xl"))
16//! .child(p().text("Ultra-fast HTML generation compiled to WebAssembly."));
17//!
18//! println!("{}", card.render_pretty());
19//! ```
20//!
21//! # Escaping
22//!
23//! Text content and attribute values are escaped **when they enter the tree**, not when it
24//! is rendered, so nothing is escaped twice and nothing is missed. [`Node::Raw`] is the
25//! documented way to inject markup you already trust.
26//!
27//! URL attributes (`href`, `src`, `action`, `srcset`, … — the list is in
28//! [`core::url_policy`]) are checked **when the tree is written**: a `javascript:` or
29//! `vbscript:` URL, or `data:text/html`, renders as `#`, and so does a `data:` document in
30//! an `iframe`, `frame`, `embed` or `object`. The check also covers a value stored with
31//! [`Attribute::raw`](core::Attribute::raw), and `srcdoc` is written empty.
32//! [`Element::trusted_url`] opts out for a URL you wrote yourself. Tag and attribute names
33//! are validated too. See `SECURITY.md`.
34//!
35//! ```
36//! use molt::prelude::*;
37//! assert_eq!(p().text("<script>").render(), "<p><script></p>");
38//! ```
39//!
40//! # Render modes
41//!
42//! [`Render::render`] produces a single line; [`Render::render_pretty`] indents. Both take
43//! their configuration from a [`RenderOptions`] value rather than global state, so two
44//! threads can render the same tree differently at the same time.
45//!
46//! # Limits
47//!
48//! Rendering has no depth limit by default: the writer walks an explicit stack instead of
49//! recursing, and so do `Clone`, `PartialEq`, `Hash` and `Drop`. What deep trees do cost is
50//! output size in pretty mode, which is quadratic in depth because every line carries one
51//! indent string per level. When depth comes from untrusted input, bound it with
52//! [`RenderOptions::with_max_depth`]. See [`Render`] and `SECURITY.md`.
53//!
54//! # Golden fixtures
55//!
56//! The rendered markup of a whole page, a sitemap and a feed is compared byte for byte with
57//! files in `tests/fixtures/`, so a change to the output of valid input cannot go unnoticed.
58//! `MIGRATION.md` lists what changed between releases.
59
60#![cfg_attr(docsrs, feature(doc_cfg))]
61// The logo and favicon shown by rustdoc (docs.rs and the Pages build). The file lives at the
62// repository root and is excluded from the package, so the URL has to be absolute.
63#![doc(html_logo_url = "https://raw.githubusercontent.com/micheltlutz/molt/main/molt-logo.png")]
64#![doc(html_favicon_url = "https://raw.githubusercontent.com/micheltlutz/molt/main/molt-logo.png")]
65
66pub mod accessibility;
67pub mod core;
68pub mod document;
69pub mod elements;
70pub mod feed;
71pub mod layout;
72#[macro_use]
73pub mod macros;
74pub mod prelude;
75pub mod seo;
76pub mod sitemap;
77#[cfg(all(feature = "ssg", not(target_arch = "wasm32")))]
78#[cfg_attr(docsrs, doc(cfg(feature = "ssg")))]
79pub mod ssg;
80#[cfg(feature = "wasm")]
81#[cfg_attr(docsrs, doc(cfg(feature = "wasm")))]
82pub mod wasm;
83
84pub use crate::core::{Attribute, Element, Node, Render, RenderOptions};
85pub use crate::document::Document;
86pub use crate::layout::Layout;
87
88/// The prose guides are doc tests too, so a snippet in them cannot rot. Gated on `ssg`
89/// because both use `molt::ssg`, and on not being `wasm32`, because there the module does not
90/// exist even with the feature on (`wasm-pack test` runs the doc tests for that target).
91/// Compiled only when running doc tests.
92#[cfg(all(doctest, feature = "ssg", not(target_arch = "wasm32")))]
93mod guides {
94 #[doc = include_str!("../GETTING_STARTED.md")]
95 mod getting_started {}
96
97 #[doc = include_str!("../MIGRATION.md")]
98 mod migration {}
99
100 #[doc = include_str!("../docs/pt-BR/GETTING_STARTED.md")]
101 mod getting_started_pt_br {}
102
103 #[doc = include_str!("../docs/performance.md")]
104 mod performance {}
105
106 #[doc = include_str!("../docs/capabilities.md")]
107 mod capabilities {}
108
109 #[doc = include_str!("../docs/pt-BR/performance.md")]
110 mod performance_pt_br {}
111}