How to Create a JSX-Like Rust Macro: Step-by-Step Guide - Part 1

Preface
I’ve had this itch to build an Web Framework. Will I actually finish it? … probably not. But hey, posting about the journey keeps me moving, one tiny experiment at a time. If you want to contribute or get 1:1 mentorship, shoot me a DM and come learn with me! Okay, let’s see where this goes!
Inspiration
Jason Miller aka DevelopIt has been a big inspiration for me for a long time. His library HTM is the library that inspired this first step towards making a web framework.
What really go this idea going on is this very recent video from Ryan Carniato the author of SolidJS... A great watch prior to this lesson
Objectives
Learn Rust
Uncompromising Performance and DX Framework
Low Code Tools
Step one: macros_rules!
There are 2 types of macros in rust "declarative" and "procedural". I'm not smart enough for "procedural", yet. This contrived example below will use "declarative" with the macro_rules! and later I'll make a follow up iteration on the more flexible and advanced procedural
Scaffold & Test
Been using the project name "Candy" and htm because of what I said above...
cargo new candy_htm --lib
cd candy_htm
Macros in rust use the ! and the format! macro is baked in to rust for string formatting/interpolation.
pub fn htm_element(tag: &str, children: &str) -> String {
format!("<{}>{}</{}>", tag, children, tag)
}
Rust comes with tests baked into the language. The syntax is a bit complicated at a glance but we can use gpt or some docs to sus it out:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn htm_element_test() {
let result = htm_element("p", "Hello, World");
assert_eq!(result, "<p>Hello, World</p>");
}
}
Let's validate that the test works:
Creating a Macro
#[macro_export]
macro_rules! htm {
(<$tag: ident> $content: literal </$end_tag: ident>) => {
$crate::htm_element(stringify!($tag), $content)
};
}
#[test]
fn htm_macro() {
let x = htm!(<p>"hello world"</p>);
assert_eq!(
x,
"<p>hello world</p>"
)
}
There is a lot of syntax to unpack here so lets go over it:
Pattern Matching
Below is a "Pattern" that literally matches on the inner htm!(<p>"hello world"</p>)
<$tag: ident> $content: literal </$end_tag: ident>
Fragment Specifiers
You still must give a macro pattern almost "types" called fragment specifiers... there are a lot’
Crates
Next we need to point our now matched macro & parsed tokens to the input of a the function using the $crate syntax
$crate::htm_element(stringify!($tag), $content)
stringify! Turns a token into a string (e.g., div → "div")
Closing
I have like 10 more steps to go
Arbitrary Rust Expressions
HTML Attributes
Allow Nested HTML
Tag Mismatch Safety
Create Component-Like Functions
Build an HTML Tree Struct
Traits for Render-to-String
Migrate to Procedural Macros
Signals / Reactivity
Subscribe to my newsletter
Read articles from Matthew Harwood directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
