MRML
Why?

- Slow and heavy
- Doesn't run in the browser
- Not integrated with other languages
What?
<mjml>...</mjml>
Parse
Render
<html>...</html>
<mjml>...</mjml>
Parse
Render
<html>...</html>
<mj-include>
VDom Like
<mjml>...</mjml>
Parse
Render
<html>...</html>
<mj-include>
VDom Like
JSON
<mjml>...</mjml>
Parse
Render
<html>...</html>
<mj-include>
VDom Like
JSON
MJML
How?
Version 1
MACROS EVERYWHERE!!!
#[macro_export]
macro_rules! print_children {
($structure:ident, $name:expr) => {
use crate::prelude::print::{self, Print};
impl Print for $structure {
fn print(&self, pretty: bool, level: usize, indent_size: usize) -> String {
print::open($name, None, false, pretty, level, indent_size)
+ &self
.children
.iter()
.map(|child| child.print(pretty, level + 1, indent_size))
.collect::<String>()
+ &print::close(super::NAME, pretty, level, indent_size)
}
}
crate::print_display!($structure);
};
}Version 2
PROC MACROS EVERYWHERE!!!
extern crate proc_macro;
mod attributes;
mod children;
mod element;
use darling::FromDeriveInput;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(MrmlPrintComponent, attributes(mrml_print))]
pub fn derive_element(input: TokenStream) -> TokenStream {
let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
let opts = element::Opts::from_derive_input(&ast).expect("Wrong options");
element::Generator::from((ast, opts)).build().into()
}
#[proc_macro_derive(MrmlPrintAttributes)]
pub fn derive_attributes(input: TokenStream) -> TokenStream {
let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
attributes::Generator::from(ast).build().into()
}
#[proc_macro_derive(MrmlPrintChildren)]
pub fn derive_children(input: TokenStream) -> TokenStream {
let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
children::Generator::from(ast).build().into()
}Version 3
More structure
- Following serde pattern
- Less cloning
- Async loaders for includes
- More...
impl<'root> Renderer<'root, MjText, ()> {
fn set_style_text<'a, 't>(&'a self, tag: Tag<'t>) -> Tag<'t>
where
'root: 'a,
'a: 't,
{
tag.maybe_add_style("font-family", self.attribute("font-family"))
.maybe_add_style("font-size", self.attribute("font-size"))
.maybe_add_style("font-style", self.attribute("font-style"))
.maybe_add_style("font-weight", self.attribute("font-weight"))
.maybe_add_style("letter-spacing", self.attribute("letter-spacing"))
.maybe_add_style("line-height", self.attribute("line-height"))
.maybe_add_style("text-align", self.attribute("align"))
.maybe_add_style("text-decoration", self.attribute("text-decoration"))
.maybe_add_style("text-transform", self.attribute("text-transform"))
.maybe_add_style("color", self.attribute("color"))
.maybe_add_style("height", self.attribute("height"))
}
}Difficulties
Parsing HTML
HTML != XML
<div class="foo">
<span>
<img>
<br>
</div
<div class="bar">
<img>
<source ... />
</img>
</divTesting is challenging
const cleanup = (content) => {
console.log(`⌛️ doing some more cleanup`);
return (
content
// empty style css blocks
.replace(/<style\s+type="text\/css">\s*<\/style>/gim, "")
.replace(/style=""/gim, "")
// empty div blocks
.replace(/<div\s*>\s*<\/div>/gim, "")
.replace(/\s{2,}/gim, "\n")
// percentages that are rounded in rust
.replace(/33\.333333333333336/gm, "33.333332")
.replace(/33-333333333333336/gm, "33-333332")
);
};Comparing carrots with cabbages
Good Feeling

Works with
Python
JavaScript (Browser)
NodeJS
Rust
Ruby
Elixir
IntelliJ
200 times faster
MRML
By Jérémie Drouet
MRML
- 45