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/// Emits bundled Surreal and css-scope-inline runtimes.
91#[proc_macro]
92pub fn surreal_scope_inline(_input: TokenStream) -> TokenStream {
93    syn::Error::new(
94        proc_macro2::Span::call_site(),
95        "`surreal_scope_inline!()` is no longer part of the preferred story; use `mx::Init::new().surrealjs().scoped_css().build()` or `mx::Init::all()` instead",
96    )
97    .to_compile_error()
98    .into()
99}
100
101/// Emits bundled Signals runtime and adapter.
102#[proc_macro]
103pub fn signals_inline(_input: TokenStream) -> TokenStream {
104    syn::Error::new(
105        proc_macro2::Span::call_site(),
106        "`signals_inline!()` is no longer part of the preferred story; use `mx::Init::new().signals().build()` or `mx::Init::all()` instead",
107    )
108    .to_compile_error()
109    .into()
110}
111
112/// Emits the full runtime bundle: Surreal, css-scope-inline, Signals core, and adapter.
113#[proc_macro]
114pub fn surreal_scope_signals_inline(_input: TokenStream) -> TokenStream {
115    syn::Error::new(
116        proc_macro2::Span::call_site(),
117        "`surreal_scope_signals_inline!()` is no longer part of the preferred story; use `mx::Init::all()` instead",
118    )
119    .to_compile_error()
120    .into()
121}
122
123/// Experimental opt-in derive for builder-centric Maud component authoring.
124///
125/// This derive is gated behind the `components` crate feature and is only a
126/// partial surface right now. The current v1 slice is Bon-backed and supports
127/// prop builders only; slot-specific ergonomics are still under construction.
128///
129/// Generated component code references `::maud_extensions::bon`. The public
130/// runtime crate re-exports Bon so downstream users only need `maud-extensions`.
131/// Repeated slot support currently relies on Bon's experimental `overwritable`
132/// feature through that re-exported dependency.
133#[cfg(feature = "components")]
134#[proc_macro_derive(Component, attributes(mx))]
135pub fn component_derive(input: TokenStream) -> TokenStream {
136    let input = parse_macro_input!(input as component::Input);
137    component::expand(input)
138}
139
140/// Removed component impl macro.
141#[cfg(feature = "components")]
142#[proc_macro_attribute]
143pub fn component(_attr: TokenStream, _input: TokenStream) -> TokenStream {
144    syn::Error::new(
145        proc_macro2::Span::call_site(),
146        "`#[mx::component]` is no longer part of the component story; use explicit `fn css() -> Markup`, `fn js() -> Markup`, and a normal `impl Render` instead",
147    )
148    .to_compile_error()
149    .into()
150}
151
152/// Removed render block macro from the component story.
153#[cfg(feature = "components")]
154#[proc_macro]
155pub fn render(_input: TokenStream) -> TokenStream {
156    syn::Error::new(
157        proc_macro2::Span::call_site(),
158        "`render!` is no longer part of the component story; write a normal `impl Render` and place `(Self::css())` / `(Self::js())` explicitly in the markup",
159    )
160    .to_compile_error()
161    .into()
162}