Crate mrml

Crate mrml 

Source
Expand description

§MRML

Crates.io Crates.io FOSSA Status

.github/workflows/main.yml codecov

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

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

§How to use?

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

[dependencies]
mrml = { version = "*" }
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::RenderOptions::default();
match root.element.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.element.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.

§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.
  • The JavaScript implementation cannot be run in the browser; the Rust one (and WebAssembly one) can be.

§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

  • Node: 606.59ms
  • Rust: 3.48ms

§Missing implementations

  • mj-style[inline]: not yet implemented. It requires parsing the generated html to apply the inline styles afterward (that’s how it’s done in mjml) which would kill the performances. Applying it at render time would improve the performance but it would still require to parse the CSS.
  • mj-include: not yet implemented. It requires to handle loading remote templates when using mrml in a wasm (browser or server side) format, which implies being able to load from a different location (file://, https://, relative, etc).

§Who is using MRML?

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.

§What is using MRML?

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.

§You want to sponsor us?

Buy Me A Coffee

Thanks to zachzurn.

§License

FOSSA Status

Modules§

comment
conditional_comment
mj_accordion
Module containing the mj-accordion element as defined in the documentation.
mj_accordion_element
mj_accordion_text
mj_accordion_title
mj_attributes
mj_attributes_all
mj_attributes_class
mj_attributes_element
mj_body
mj_breakpoint
mj_button
mj_carousel
mj_carousel_image
mj_column
mj_divider
mj_font
mj_group
mj_head
mj_hero
mj_image
mj_include
mj_navbar
mj_navbar_link
mj_preview
mj_raw
mj_section
mj_social
mj_social_element
mj_spacer
mj_style
mj_table
mj_text
mj_title
mj_wrapper
mjml
node
prelude
text

Functions§

parse
Function to parse a raw mjml template using the default parsing options.
parse_with_options
Function to parse a raw mjml template with some parsing options. This function is just an alias to the Mjml::parse_with_options function.