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>
</div

Testing 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