Home

Awesome

MRML

Crates.io Crates.io FOSSA Status

.github/workflows/main.yml codecov

Average time to resolve an issue Percentage of issues still open Maintainability

Introduction

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

How to use it in my code

Update your cargo.toml:

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

Create your main.rs:

use mrml::prelude::parser::http_loader::{HttpIncludeLoader, BlockingReqwestFetcher};
use mrml::prelude::parser::ParserOptions;
use mrml::prelude::render::RenderOptions;
use std::collections::HashSet;


fn main() {
  let resolver = HttpIncludeLoader::<BlockingReqwestFetcher>::new_allow(HashSet::from(["http://localhost".to_string()]));
  let parser_options = ParserOptions {
      include_loader: Box::new(resolver),
  };
  let render_options = RenderOptions::default();
  let template = r#"<mjml>
  <mj-body>
    <mj-include path="http://localhost/partials/mj-body.mjml" />
  </mj-body>
</mjml>"#;
  match mrml::parse_with_options(template, &parser_options) {
      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:?}"),
  }
}

It's also possible to use an async include loader

use mrml::mj_include::body::MjIncludeBodyKind;
use mrml::prelude::parser::http_loader::{AsyncReqwestFetcher, HttpIncludeLoader};
use mrml::prelude::parser::local_loader::LocalIncludeLoader;
use mrml::prelude::parser::memory_loader::MemoryIncludeLoader;
use mrml::prelude::parser::multi_loader::{MultiIncludeLoader, MultiIncludeLoaderItem, MultiIncludeLoaderFilter};
use mrml::prelude::parser::noop_loader::NoopIncludeLoader;
use mrml::prelude::parser::loader::AsyncIncludeLoader;
use mrml::prelude::parser::AsyncParserOptions;
use mrml::prelude::render::RenderOptions;

#[tokio::main]
async fn main() {
  let resolver = MultiIncludeLoader::<Box<dyn AsyncIncludeLoader + Send + Sync + 'static>>::new()
      .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-body>
</mjml>"#;
  match mrml::async_parse_with_options(json, std::sync::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:?}"),
  }
}

Why?

You want to contribute?

Feel free to read our contributing section and the code of conduct.

Performance

With the same Linux amd64 machine, to render the amario template using hyperfine (see the script in the benchmarks folder).

Benchmark 1: mjml /amario.mjml
  Time (mean ± σ):     634.1 ms ±   5.2 ms    [User: 669.3 ms, System: 168.2 ms]
  Range (min … max):   625.8 ms … 642.3 ms    10 runs

Benchmark 2: /usr/bin/mrml /amario.mjml render
  Time (mean ± σ):       5.6 ms ±   0.1 ms    [User: 2.8 ms, System: 2.9 ms]
  Range (min … max):     5.5 ms …   7.1 ms    494 runs

Summary
  /usr/bin/mrml /amario.mjml render ran
  112.83 ± 2.12 times faster than mjml /amario.mjml

From this, you can see that mrml is more than 110 faster than mjml.

Missing implementations

Who is using MRML?

<img src="https://www.blizzstatic.com/www/marketing/images/logo.svg" height="22px" />

<i>If you are using MRML and want to be added to this list, don't hesitate to create an issue or open a pull request.</i>

What is using MRML?

mjml_nif - Elixir library

mrml-ruby - Ruby library

mjml-python - Python library

<i>If you are using MRML and want to be added to this list, don't hesitate to create an issue or open a pull request.</i>

License

FOSSA Status