Skip to main content

maud_extensions_macros/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3#![deny(rustdoc::broken_intra_doc_links)]
4// Public proc-macro surface: canonical css!/js!/Component entrypoints live here.
5//! Local CSS and JS emitters for Maud.
6//!
7//! The reset story for this branch is intentionally small:
8//! - write plain `html!`
9//! - emit local CSS with `css!`
10//! - emit local JS with `js!`
11//! - optionally opt into experimental component authoring with `Component`
12//!
13//! `maud-extensions` should feel like tiny Maud superpowers, not a framework.
14
15#[cfg(feature = "components")]
16mod component;
17mod css;
18mod internal_diagnostics;
19mod js;
20mod raw_text;
21
22use proc_macro::TokenStream;
23use syn::parse_macro_input;
24
25/// Emits a `<style>` tag or defines a named local CSS helper.
26///
27/// Supported forms:
28/// - `css! { ... }`
29/// - `css!(name, { ... })`
30///
31/// ```ignore
32/// use maud::html;
33/// use maud_extensions::{css, js};
34///
35/// fn view() -> maud::Markup {
36///     css!(responsive_css, {
37///         media!("(min-width: 48rem)", {
38///             me { padding: rem!(2); }
39///         })
40///     });
41///
42///     html! {
43///         article.card {
44///             "Hello"
45///             (css! {
46///                 me { color: red; }
47///             })
48///             (responsive_css())
49///             (js! {
50///                 me().class_add("ready");
51///             })
52///         }
53///     }
54/// }
55/// ```
56#[proc_macro]
57pub fn css(input: TokenStream) -> TokenStream {
58    let input = parse_macro_input!(input as css::MacroInput);
59    css::expand(input)
60}
61
62/// Emits a `<script>` tag or defines a named local JS helper.
63///
64/// Supported forms:
65/// - `js! { ... }`
66/// - `js!(once, { ... })`
67/// - `js!(name, { ... })`
68/// - `js!(name, once, { ... })`
69///
70/// ```ignore
71/// use maud::html;
72/// use maud_extensions::js;
73///
74/// fn view() -> maud::Markup {
75///     html! {
76///         div {
77///             (js!(once, {
78///                 me().class_add("ready");
79///             }))
80///         }
81///     }
82/// }
83/// ```
84#[proc_macro]
85pub fn js(input: TokenStream) -> TokenStream {
86    let input = parse_macro_input!(input as js::MacroInput);
87    js::expand(input)
88}
89
90/// Experimental opt-in derive for builder-centric Maud component authoring.
91///
92/// This derive is gated behind the `components` crate feature and is only a
93/// partial surface right now. The current v1 slice is Bon-backed and supports
94/// prop builders only; slot-specific ergonomics are still under construction.
95///
96/// Generated component code references `::maud_extensions::bon`. The public
97/// runtime crate re-exports Bon so downstream users only need `maud-extensions`.
98/// Repeated slot support currently relies on Bon's experimental `overwritable`
99/// feature through that re-exported dependency.
100#[cfg(feature = "components")]
101#[proc_macro_derive(Component, attributes(mx))]
102pub fn component_derive(input: TokenStream) -> TokenStream {
103    let input = parse_macro_input!(input as component::Input);
104    component::expand(input)
105}
106
107/// Experimental component impl macro for colocated render/css/js blocks.
108#[cfg(feature = "components")]
109#[proc_macro_attribute]
110pub fn component(_attr: TokenStream, input: TokenStream) -> TokenStream {
111    let input = parse_macro_input!(input as syn::ItemImpl);
112    component::expand_impl(input)
113}