Crate mrml

source ·
Expand description

This project is a reimplementation of the nice MJML markup language in Rust.

.github/workflows/main.yml codecov Maintainability

§How to use?

To use it you can simply update your Cargo.toml by adding

[dependencies]
mrml = { version = "3" }
serde = { version = "1", features = ["derive"] }

And you can then just create a main.rs with the following code

let root = mrml::parse("<mjml><mj-body></mj-body></mjml>").expect("parse template");
let opts = mrml::prelude::render::Options::default();
match root.render(&opts) {
    Ok(content) => println!("{}", content),
    Err(_) => println!("couldn't render mjml template"),
};

§Using mj-include

You can also use the mj-include component by specifying a loader.

use mrml::prelude::parser::ParserOptions;
use mrml::prelude::parser::memory_loader::MemoryIncludeLoader;

let loader = MemoryIncludeLoader::from(vec![("partial.mjml", "<mj-button>Hello</mj-button>")]);
let options = ParserOptions {
    include_loader: Box::new(loader),
};
match mrml::parse_with_options("<mjml><mj-head /><mj-body><mj-include path=\"partial.mjml\" /></mj-body></mjml>", &options) {
    Ok(_) => println!("Success!"),
    Err(err) => eprintln!("Something went wrong: {err:?}"),
}

§Using mj-include with an async loader

If you want to use the async version to fetch the includes, you’ve to enable the async feature and the required loaders (http-loader-async-reqwest in this example).

use mrml::prelude::parser::http_loader::{AsyncReqwestFetcher, HttpIncludeLoader};
use mrml::prelude::parser::memory_loader::MemoryIncludeLoader;
use mrml::prelude::parser::local_loader::LocalIncludeLoader;
use mrml::prelude::parser::multi_loader::MultiIncludeLoader;
use mrml::prelude::parser::noop_loader::NoopIncludeLoader;
use mrml::prelude::parser::loader::AsyncIncludeLoader;
use mrml::prelude::parser::AsyncParserOptions;
use mrml::prelude::render::RenderOptions;
use std::path::PathBuf;
use std::sync::Arc;

let resolver = MultiIncludeLoader::<Box<dyn AsyncIncludeLoader + Send + Sync + 'static>>::new()
    .with_starts_with("memory://", Box::new(MemoryIncludeLoader::from(vec![("basic.mjml", "<mj-button>Hello</mj-button>")])))
    .with_starts_with("file://", Box::new(LocalIncludeLoader::new(PathBuf::default().join("resources").join("compare").join("success"))))
    .with_starts_with("https://", Box::new(HttpIncludeLoader::<AsyncReqwestFetcher>::allow_all()))
    .with_any(Box::<NoopIncludeLoader>::default());
let parser_options = AsyncParserOptions {
    include_loader: Box::new(resolver),
};
let render_options = RenderOptions::default();
let json = r#"<mjml>
<mj-body>
<mj-include path="file://basic.mjml" />
<mj-include path="memory://basic.mjml" />
</mj-body>
</mjml>"#;
match mrml::async_parse_with_options(json, Arc::new(parser_options)).await {
    Ok(mjml) => match mjml.render(&render_options) {
        Ok(html) => println!("{html}"),
        Err(err) => eprintln!("Couldn't render template: {err:?}"),
    },
    Err(err) => eprintln!("Couldn't parse template: {err:?}"),
}

§Using mrml in Python

This crate can also be used in Python. The crate is available with pypi and you can find some documentation here.

import mrml

# without options
result = mrml.to_html("<mjml></mjml>")
assert result.startswith("<!doctype html>")

# with options
parser_options = mrml.ParserOptions(include_loader = mrml.memory_loader({
    'hello-world.mjml': '<mj-text>Hello World!</mj-text>',
}))
result = mrml.to_html("<mjml><mj-body><mj-include path=\"hello-world.mjml\" /></mj-body></mjml>", parser_options = parser_options)
assert result.startswith("<!doctype html>")

§Why?

A Node.js server rendering an MJML template takes around 20 MB of RAM at startup and 130 MB under stress test. In Rust, less than 1.7 MB at startup and a bit less that 3 MB under stress test. The Rust version can also handle twice as many requests per second. You can perform the benchmarks by running bash script/run-bench.sh.

Also, the JavaScript implementation cannot be run in the browser; the Rust one (and WebAssembly one) can be.

Modules§

Macros§

Functions§