Giter Site home page Giter Site logo

mrml's Introduction

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?

  • 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 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 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

  • 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.

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?

mjml_nif - Elixir library

mrml-ruby - Ruby library

mjml-python - Python library

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.

License

FOSSA Status

mrml's People

Contributors

bl-ue avatar christophehenry avatar dependabot[bot] avatar fossabot avatar github-actions[bot] avatar glours avatar jadedblueeyes avatar jdrouet avatar jonian avatar marcantoine-arnaud avatar snyk-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

mrml's Issues

Ability to provide mjml as input vs a file path

Using mrml-cli from a legacy PHP codebase where the MJML is dynamically generated. In order to use the cli, a temp file has to be written to that has a .mjml extension in order to use the cli.

Sample of the current approach from PHP:

//Create a unique temp file
$tempPath = tempnam(sys_get_temp_dir(), 'mjml-compiler-');
//Hacky, use the same path but with the mjml extension
$tempMjmlPath = $tempPath . '.mjml';

//Write the MJML to the file
file_put_contents($tempMjmlPath, $mjml);

//Run the file through the cli
$arguments = [
    __DIR__ . '/../../../bin/mrml', //Executable path
    $tempMjmlPath, //mjml path
    'render', //The command
];

$process = new Process($arguments);

try {
    //Run and return output
    $process->mustRun();
    $output = $process->getOutput();
    return $output;
} catch (ProcessFailedException $e) {
    //Fallback to the api until mrml can have a non strict mode (A ticket exists for this)
    return self::apiRender($mjml);
} finally {
    unlink($tempPath); //delete the empty file that tempnam creates
    unlink($tempMjmlPath); //to delete the temp MJML file    
}

Possibly if no path is provided, for example mjml-cli render the cli could ask for input instead which would avoid the need to write to and read from a file.

Sample with using cli input method:

//Run the file through the cli
$arguments = [
    __DIR__ . '/../../../bin/mrml', //Executable path
    $tempMjmlPath, //mjml path
    'render', //The command
];

$process = new Process($arguments);
$process->setInput($mjml);

try {
    //Run and return output
    $process->mustRun();
    $output = $process->getOutput();
    return $output;
} catch (ProcessFailedException $e) {
    //Fallback to the api until mrml can have a non strict mode (A ticket exists for this)
    return self::apiRender($mjml);
}

Default fonts does not seems to behave as a fallback

I just discover this awesome lib, thanks for the great work!

Redefining in template one of the default -or configured- font seems to duplicate definition instead of replacing it. for instance with Roboto

<mjml>
  <mj-head>
    <mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i" />
  </mj-head>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text font-family="Roboto, Arial">
          Hello World!
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

produces

<link href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i" rel="stylesheet" type="text/css">
<style type="text/css">
  @import url(https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i);
</style>

using MJML,
but duplicates the definitions with MRML:

<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i" rel="stylesheet" type="text/css">
<style type="text/css">
  @import url(https://fonts.googleapis.com/css?family=Roboto:300,400,500,700);
  @import url(https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i);
</style>

I am not a rust developer, but a simple fix could look like

diff --git a/packages/mrml-core/src/mj_head/render.rs b/packages/mrml-core/src/mj_head/render.rs
index d377bdc..9b2f617 100644
--- a/packages/mrml-core/src/mj_head/render.rs
+++ b/packages/mrml-core/src/mj_head/render.rs
@@ -145,23 +145,25 @@ impl<'e, 'h> MjHeadRender<'e, 'h> {
         header
             .used_font_families()
             .iter()
-            .filter_map(|name| opts.fonts.get(name))
+            .filter_map(|name| header.font_families().get(name.as_str()))
             .for_each(|href| links.push_str(&self.render_font_link(href)));
         header
             .used_font_families()
             .iter()
-            .filter_map(|name| header.font_families().get(name.as_str()))
+            .filter(|name| !header.font_families().get(name.as_str()).is_some())
+            .filter_map(|name| opts.fonts.get(name))
             .for_each(|href| links.push_str(&self.render_font_link(href)));
         let mut imports = String::default();
         header
             .used_font_families()
             .iter()
-            .filter_map(|name| opts.fonts.get(name))
+            .filter_map(|name| header.font_families().get(name.as_str()))
             .for_each(|href| imports.push_str(&self.render_font_import(href)));
         header
             .used_font_families()
             .iter()
-            .filter_map(|name| header.font_families().get(name.as_str()))
+            .filter(|name| !header.font_families().get(name.as_str()).is_some())
+            .filter_map(|name| opts.fonts.get(name))
             .for_each(|href| imports.push_str(&self.render_font_import(href)));
         if links.is_empty() && imports.is_empty() {
             String::default()

While using more than 4 <tr/> tags inside <table> tag compilation is failing.

Tried for below MJML code snippet.

<mjml lang="en"> <mj-body> <mj-section> <mj-column> <mj-table> <tr> <td> <img src="https://picsum.photos/200/300"> </img> </td> <td> <img src="https://picsum.photos/200/300"> </img> </td> <td> <img src="https://picsum.photos/200/300"> </img> </td> <td> <img src="https://picsum.photos/200/300"> </img> </td> <td> <img src="https://picsum.photos/200/300"> </img> </td> <td> <img src="https://picsum.photos/200/300"> </img> </td> <td> <img src="https://picsum.photos/200/300"> </img> </td> </tr> </mj-table> </mj-column> </mj-section> </mj-body> </mjml>

Screenshot 2022-12-06 at 4 19 40 PM

Invalid nested HTML comments in generated MSO conditionals

I just updated mjml-python from 1.2.3 to 1.3.0, (which as far as I can gather changes the linked MRML from version 2.0.0-rc3 to 3.0.0). This caused our do our emails parse as HTML canary to trip up on output generated from MJML code like this:

<mjml>
    <mj-body>
        <mj-section>
        <!-- A comment -->
        </mj-section>
    </mj-body>
</mjml>

I have cut the generated code to only show the relevant bits, but added newlines and indentation to the MRML generated code.

mjml-python 1.3.0 (mrml 3.0.0)

<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
  <!--[if mso | IE]><table border="0" cellpadding="0" cellspacing="0" role="presentation"><tr><!-- A comment --></tr></table><![endif]-->
</td>

mjml-python 1.2.3 (mrml 2.0.0-rc3)

<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
  <!--[if mso | IE]><table border="0" cellpadding="0" cellspacing="0" role="presentation"><![endif]--><!--[if mso | IE]><tr><![endif]-->
  <!-- A comment -->
  <!--[if mso | IE]></tr><![endif]--><!--[if mso | IE]></table><![endif]-->
</td>

mjml.io

<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
  <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><![endif]-->
  <!-- A comment -->
  <!--[if mso | IE]></tr></table><![endif]-->
</td>

It looks like mjml.io's implementation detects the comment and ends the MSO conditional, and that MRML prior to version 2.0.0 had the same behaviour. I'm not sure if the behaviour change in later versions is intentional, or if a dependency caused this change. However, the new behaviour might be surprising to users since it is different from that of the reference MJML implementation (and generates a nested comment, i.e. invalid HTML).

Personally I am considering stripping HTML comments before passing the template to MRML anyway.

Thanks for a great library though!

split the lifecycles

I should be able to parse the template and detect if there is an error.
I should be able to build a template and then pretty print it
I should be able to parse the template, save the memory state and render it several time

Allow arbitrary value inside mj-raw

First of all thanks for the awesome library.

I'm trying to add templating tags directly inside mjml. According to the docs https://documentation.mjml.io/#mj-raw we can accomplish it using the mj-raw tag.

Right now mrml seems to support only valid html inside the tags. Would be nice if we could pass any arbitrary value inside the mjml to be able to latter pass the generated html to our templating engine.

Examples of the mjml code:

<mjml>
  <mj-body>
    <mj-raw>
      {% if foo < 5 %}
    </mj-raw>
      <!-- Some mjml section -->
    <mj-raw>
      {% endif %}
    </mj-raw>
  </mj-body>
</mjml>
<mjml>
  <mj-body>
    <mj-raw>
      <%= @foo %>
    </mj-raw>
  </mj-body>
</mjml>

If this feature is welcoming, I would be happy to send a PR for it, I would probably just need to some guidance on the code.

MJML Contrib

Hi,

I am the co-contributor of a .NET port: https://github.com/SebastianStehle/mjml-net and @skybber is working on a Java port which is based on .NET and the reference implementation. Our goals are similar: To be as fast as possible.

I think we have a few challenges in common:

  1. Discoverability: MJML is used a lot but only the official Node implementation:
  • I think we should ask the mjml team to provide a links to alternative implementations.
  • Furthermore It would make sense to combine our implementations under a Github Orga. I recommend "mjml-contrib", because I have seen this naming pattern very often.
  1. Testing: As much as I admire the work of the mjml-team, the test coverage is horrible. The main challenge is to test our implementation to work. We have written a few tests to compare the official templates with our implementation, but it was a challenge to make the HTML comparison working properly:
  • We could provide a CLI or tooling to test all implementations in one test suite.
  • We could use image snapshot testing to compare results (I have done this for another project).

Are you interested in these topics?

mjml "owa" attribute is causing panic UnexpectedAttribute(16)

I was running into this error message Failed to parse template: UnexpectedAttribute(16) after adding owa="desktop" into the MJML template like this: <mjml lang="en" owa="desktop">. From this recent PR, the attribute should be supported as an optional attribute. Is it possible to support this feature?

Environment: macOS 11.5.2 (20G95)
Version: mrml v1.2.7

When I run this template on the live sandbox, I'm not facing any error

Missing "derive" feature for serde dependency?

Hey Jérémie, I was just trying to update the Elixir mjml_nif lib to use mrml v1.2.2 and came across this error when compiling the crate:

Compiling mrml v1.2.2
error[E0433]: failed to resolve: could not find `Serialize` in `serde`
  --> /home/paul/.asdf/installs/rust/1.51.0/registry/src/github.com-1ecc6299db9ec823/mrml-1.2.2/src/mj_accordion/children.rs:16:44
   |
16 | #[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
   |                                            ^^^^^^^^^ could not find `Serialize` in `serde`

error[E0433]: failed to resolve: could not find `Deserialize` in `serde`
  --> /home/paul/.asdf/installs/rust/1.51.0/registry/src/github.com-1ecc6299db9ec823/mrml-1.2.2/src/mj_accordion/children.rs:16:62
   |
16 | #[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
   |                                                              ^^^^^^^^^^^ could not find `Deserialize` in `serde`

The dependencies in my Cargo.toml looked like this when compiling failed:

[dependencies]
rustler = "0.22.0-rc.1"
mrml = "1.2.2"

When I put an additonal explicit serde dependency with reference to its derive feature, like the following, compiling succeeds:

[dependencies]
rustler = "0.22.0-rc.1"
serde = { version = "1.0", features = ["derive"] }
mrml = "1.2.2"

(This also fetches an additional serde_derive v1.0.126)

I'm not too familiar with serde and the usage of macros, but might there be a missing features = ["derive"] for the serde dependency (or an explicit serde_derive dependency) in the mrml-core Cargo.toml?

Or did I miss sth. that changed from mrml v1.0.0 to v1.2.2 in terms of setup/installation?

mj-style is not outputting start and end style tags

When using the mj-style tag with no attributes:

<mj-style> a {} p {} h1 {} h2 {} h3 {} button {} </mj-style>

The output is put into the html head but without any surrounding <style></style> tags.

a {} p {} h1 {} h2 {} h3 {} button {}

mj-class ignored

When running the following example with the latest version of MRML the mj-class elements seem to be ignored https://github.com/jdrouet/mrml/blob/main/resources/compare/success/mj-attributes.mjml

I was attempting to upgrade from v0.3.0 to v1.2.2 when I noticed the issue.

<mjml>
  <mj-head>
    <mj-attributes>
      <mj-text padding="0" />
      <mj-class name="blue" color="blue" />
      <mj-class name="big" font-size="20px" />
      <mj-all font-family="Arial" />
    </mj-attributes>
  </mj-head>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text mj-class="blue big">
          Hello World!
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

Expected Result

<td align="left" style="font-size:0px;padding:0;word-break:break-word;">
  <div style="font-family:Arial;font-size:20px;line-height:1;text-align:left;color:blue;">Hello World!</div>
</td>

Actual Result

<td align="left" style="font-size:0px;padding:0;word-break:break-word;">
  <div style="font-family:Arial;font-size:13px;line-height:1;text-align:left;color:#000000;">Hello World!</div>
</td>

Update expected results

Add a script that takes the mjml templates, converts them into html and open a PR , to stay aligned with mjml standard.

<mj-font> tag not valid anymore?

Hey @jdrouet, I was updating the Elixir mjml_nif package to use mrml v2.1.1 and saw an issue with rendering previously valid MJML templates that included an <mj-font> tag. (Also see adoptoposs/mjml_nif#119)

It looks like if I remove the <mj-font> tag from the template and instead pass the fonts render option, then rendering works again as expected.

Is the <mj-font> tag not handled anymore in v2.1.1 or should either way of configuring fonts in the MJML template work?

Panic rendering HTML inside Ending Tag

Hey, thank you so much for creating this amazing cli :)

I was testing some code and found out that using HTML inside mj-social-element panicked the mrml-cli compiler.

Sample:

<mjml>
  <mj-body>
    <mj-section background-color="#e7e7e7">
      <mj-column>
        <mj-social>
          <mj-social-element name="facebook">
            Share <b>test</b> hi
          </mj-social-element>
        </mj-social>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

Output:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "unable to parse mjml: UnexpectedElement(166)"', /home/mafios/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:52:40
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

From the docs the inner content should be rendered just like mj-raw/mj-text:

Some of the mjml components are "ending tags". These are mostly the components that will contain text contents, like mj-text or mj-buttons. These components can contain not only text, but also any HTML content

Here is the list of all ending tags : - mj-accordion-text - mj-accordion-title - mj-button - mj-navbar-link - mj-raw - mj-social-element - mj-text - mj-table

Refs:
https://documentation.mjml.io/#ending-tags

Style attributes randomly reorder causing inconsistent styles

Styles take priority based on where they are in the css list. These properties are randomly ordered which causes style shifts when rendering the same mjml.

For example this set of style rules would end up adding a padding-top of 50px:

padding: 0;
padding-top: 50px;

But with random ordering, this happens which makes the padding-top come out as 0:

padding-top: 50px;
padding: 0;

Styles should remain in the same order in which they were defined, while also honoring the apply order that is mentioned in the mjml docs:

Note that the apply order of attributes is: inline attributes, then classes, then default mj-attributes and then defaultMJMLDefinition

  • inline
  • classes
  • mj-attributes
  • default mjml styles

mj-wrapper rendering issues on Window Desktop Outlook Client

Hello! It's me again. We're noticing a layout issues specific only to Window Desktop Outlook client platform (macOs works fine). We're using <mj-wrapper> to wrap multiple sections, but on these platform, the sections appears to be stacking in the single row. Example:

image

After diving down deeper, I realized the diff between mrml generated html vs mjml generated html are the following (for each sections within a wrapper):

MJML

<!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><![endif]-->

MRML

<!--[if mso | IE]></td></tr></table><![endif]-->
<!--[if mso | IE]></td><td width="600px"><![endif]-->

Notice that there's a missing </tr><tr> in between </td><td width="600px"> in the MRML version. Once I replaced all of these differences, the layout works correctly for all platforms.

Let me know what you think, if it's a simple fix I can help put a PR, but your guidance might be required cause I'm still new to Rust :D

mrml produces incorrect output

It seems having an mj-column with inner-background-color breaks bg color on every element inside it:

<mjml>
<mj-body>
  <mj-section>
    <mj-column padding="1px" background-color="#E7E7E7" inner-background-color="#fff" border-radius="6px" inner-border-radius="6px">
      <mj-text>
        Hello World!
      </mj-text>
    </mj-column>
  </mj-section>
</mj-body>
</mjml>

Renders correctly on mjml, but is wrong in mrml:
Screenshot 2023-01-10 at 16 41 31

mj-raw incorrectly omits closing tag

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-raw><script src="http://example.com/hello.js"></script></mj-raw>
        <mj-text>
          Hello World!
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

mjml.io output fragment:

<script src="http://example.com/hello.js"></script>

mrml output fragment

<script src="http://example.com/hello.js" />

The documentation states that for script tags:

Neither tag is omissible.

This can be worked around by using inline documentation for external scripts:

<mj-raw><script src="http://example.com/hello.js">//</script></mj-raw>

which causes the end tag to remain when rendering using mrml. This is however inconsistent with the behaviour of mjml.io.

(Before someone tells me not to include script tags in emails, I'm not! 😄 But I am displaying rendered content in a browser, where I use script tags)

mj-include for css does not work

I have tried both of the following:

# in style.css
.container { background-color: #fffaee; padding: 48px 0px; }

# in template.mjml
<mjml>
  <mj-head>
    <mj-include path="style.css" type="css" />
  </mj-head>
  <mj-body>
  ...
  </mj-body>
</mjml>
# in style.mjml
<mj-style>
.container { background-color: #fffaee; padding: 48px 0px; }
</mj-style>

# in template.mjml
<mjml>
  <mj-head>
    <mj-include path="style.mjml" />
  </mj-head>
  <mj-body>
  ...
  </mj-body>
</mjml>

Neither of which ends up in the output HTML. I see this isn't expected to work with inline but wanted to know if this should work.

Undefined, Unknown tag. Better output? fail safely ?

Hello I found this repo in reddit and have glanced at the source code.
I've tried the wasm example to quickly go thru a few templates.

While trying to render this template https://mjml.io/try-it-live/templates/austin
the output becomes 'undefined' which I know means that it has found a tag that mrml
does not recognize but the error is not very user friendly.

I'm new to mjml and have no idea which tags are valid so I don't know how to begin to debug this. Could there be a way to ignore unknown tags and continue rendering the known tags? maybe by ignoring all the child xml nodes.

More verbose output could also be helpful.

Support for mj-raw in mj-head

Using mj-raw in mj-head breaks the mrml-cli compiler.

The documentation for this is in a weird place on the mjml docs under 'Standard Body Components' but the <mj-raw> tag can actually be used within <mj-body> and <mj-head> components.

From the docs:

Displays raw HTML that is not going to be parsed by the MJML engine. Anything left inside this tag should be raw, responsive HTML. If placed inside <mj-head>, its content will be added at the end of the <head>.

Refs:
https://documentation.mjml.io/#mj-raw

Support parsing elements with no closing tag

I am trying to parse MJML that includes <br> tags, but I get the following error: invalid format. JavaScript parser for MJML doesn't seem to have to this issue. For example, the following MJML renders without any issues on https://mjml.io/try-it-live:

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text><br>Some Test Content</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

It works when I replace <br> with <br/> but I am trying to parse some external templates and in some of these templates, <br> tags have additional data attributes, so I can't do a simple find and replace.

Please let me know if this can be supported.

UnexpectedToken with HTML comments in mj-head

In my project I was using MRML to parse a complex mjml template, but I obtained every time the error

     Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/mjml_test`
thread 'main' panicked at src/main.rs:10:38:
parse template: UnexpectedToken(Span { start: 25, end: 45 })
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

After some trial and error I found out that when I have a HTML comment like <!-- things --> anywhere inside the <mj-head> block the parser fails with UnexpectedToken targeted exactly at the comment.

The example template I used to have the previous error:

<mjml>
  <mj-head>
      <!-- TODO things -->
    <mj-preview>
    </mj-preview>
  </mj-head>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-image width="100px" src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"></mj-image>
        <mj-divider border-color="#F45E43"></mj-divider>
        <mj-text font-size="20px" color="#F45E43" font-family="helvetica">Hello Test World</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

The parser crashes even if the comment is inside <mj-preview> but not if it's inside <mj-body>

Edit: I'm using MJML version 2.1.1 but I've found the problem is present on the last version 3.0.1 too

Make IncludeLoader async

To be able to use the IncludeLoader in the browser and request partial templates from the network, we need to be able to do some asynchronous requests. This requires to create an AsyncParser trait to handle this.

Mjml validation

When building mjml templates errors sometime sneak in. The templates are often large/complex, and debugging the root cause can be difficult.

I'd like to pass a template string to a MRML validate method, and have it returns a collection of the errors it has encountered. An output similar to what mjml-cli -v produces would be amazing:

$ mjml -v malformed.mjml
Line 8 of test.mjml (b) — Element b doesn't exist or is not registered

Command line error:
Validation failed

Is this something you'd be open to?

Support for lang attribute in mjml tag

👋

When adding the lang attribute to the <mjml> tag, mrml-cli sees it as an error.

MJML currently supports this attribute and adds the lang attribute into the html tag.

<mjml lang="en-US">

becomes

<html lang="en-US" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">

References:
Github commit: mjmlio/mjml@c700c56

Docs: https://documentation.mjml.io/#mjml

License question

Hi,

Thanks for all the hard work on this project!

I maintain a Python package that uses this library as implementation, and I'd like to set a license on the package.

The repo is MIT as it only includes code by me and contributors, but I am not sure about the built binaries (derived from your source, extended with my own code).

Can you explain what (if any) conditions are required by your license, in layman's terms, relevant to my situation?

Thanks!

mrml-cli crate fails to compile when installed

Installing the mrml-cli crate yields a bunch of compile errors due to Clap macros:

monkatraz@pop-os:~$ cargo install mrml-cli
    Updating crates.io index
  Installing mrml-cli v1.3.2
   Compiling libc v0.2.110
   Compiling proc-macro2 v1.0.33
   Compiling cfg-if v1.0.0
   Compiling unicode-xid v0.2.2
   Compiling memchr v2.4.1
   Compiling syn v1.0.82
   Compiling serde_derive v1.0.131
   Compiling ryu v1.0.6
   Compiling serde v1.0.131
   Compiling autocfg v1.0.1
   Compiling log v0.4.14
   Compiling serde_json v1.0.72
   Compiling ppv-lite86 v0.2.15
   Compiling termcolor v1.1.2
   Compiling regex-syntax v0.6.25
   Compiling itoa v0.4.8
   Compiling hashbrown v0.11.2
   Compiling bitflags v1.3.2
   Compiling xmlparser v0.13.3
   Compiling textwrap v0.14.2
   Compiling humantime v2.1.0
   Compiling strsim v0.10.0
   Compiling indexmap v1.7.0
   Compiling aho-corasick v0.7.18
   Compiling os_str_bytes v6.0.0
   Compiling getrandom v0.2.3
   Compiling atty v0.2.14
   Compiling quote v1.0.10
   Compiling rand_core v0.6.3
   Compiling clap v3.0.0-rc.3
   Compiling rand_chacha v0.3.1
   Compiling regex v1.5.4
   Compiling rand v0.8.4
   Compiling env_logger v0.8.4
   Compiling mrml v1.2.6
   Compiling mrml-cli v1.3.2
error[E0432]: unresolved imports `clap::crate_authors`, `clap::crate_description`, `clap::crate_name`, `clap::crate_version`, `clap::Clap`
 --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:1:12
  |
1 | use clap::{crate_authors, crate_description, crate_name, crate_version, Clap};
  |            ^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^^^^  ^^^^ no `Clap` in the root
  |            |              |                  |           |
  |            |              |                  |           no `crate_version` in the root
  |            |              |                  no `crate_name` in the root
  |            |              no `crate_description` in the root
  |            no `crate_authors` in the root

error: cannot determine resolution for the derive macro `Clap`
 --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:8:10
  |
8 | #[derive(Clap)]
  |          ^^^^
  |
  = note: import resolution is stuck, try simplifying macro imports

error: cannot find attribute `clap` in this scope
 --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:9:3
  |
9 | #[clap(name = crate_name!(), about = crate_description!(), version = crate_version!(), author = crate_authors!())]
  |   ^^^^
  |
  = note: `clap` is in scope, but it is a crate, not an attribute

error: cannot find attribute `clap` in this scope
  --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:11:7
   |
11 |     #[clap(subcommand)]
   |       ^^^^
   |
   = note: `clap` is in scope, but it is a crate, not an attribute

error: cannot find attribute `clap` in this scope
  --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:13:7
   |
13 |     #[clap(about = "Path to your mjml file", index = 1)]
   |       ^^^^
   |
   = note: `clap` is in scope, but it is a crate, not an attribute

error: cannot determine resolution for the derive macro `Clap`
  --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:78:10
   |
78 | #[derive(Clap)]
   |          ^^^^
   |
   = note: import resolution is stuck, try simplifying macro imports

error: cannot find attribute `clap` in this scope
  --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:80:7
   |
80 |     #[clap(about = "Format template to JSON")]
   |       ^^^^
   |
   = note: `clap` is in scope, but it is a crate, not an attribute

error: cannot find attribute `clap` in this scope
  --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:82:7
   |
82 |     #[clap(about = "Format template to MJML")]
   |       ^^^^
   |
   = note: `clap` is in scope, but it is a crate, not an attribute

error: cannot find attribute `clap` in this scope
  --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:84:7
   |
84 |     #[clap(about = "Render template to HTML")]
   |       ^^^^
   |
   = note: `clap` is in scope, but it is a crate, not an attribute

error: cannot find attribute `clap` in this scope
  --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:86:7
   |
86 |     #[clap(about = "Read input file and validate its structure")]
   |       ^^^^
   |
   = note: `clap` is in scope, but it is a crate, not an attribute

error: cannot determine resolution for the derive macro `Clap`
   --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:122:10
    |
122 | #[derive(Clap)]
    |          ^^^^
    |
    = note: import resolution is stuck, try simplifying macro imports

error: cannot find attribute `clap` in this scope
   --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:124:7
    |
124 |     #[clap(long, about = "Pretty print")]
    |       ^^^^
    |
    = note: `clap` is in scope, but it is a crate, not an attribute

error: cannot determine resolution for the derive macro `Clap`
   --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:128:10
    |
128 | #[derive(Clap)]
    |          ^^^^
    |
    = note: import resolution is stuck, try simplifying macro imports

error: cannot find attribute `clap` in this scope
   --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:130:7
    |
130 |     #[clap(short, long, about = "Remove comments from html output")]
    |       ^^^^
    |
    = note: `clap` is in scope, but it is a crate, not an attribute

error: cannot find attribute `clap` in this scope
   --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:132:7
    |
132 |     #[clap(short, long, about = "Base url for social icons")]
    |       ^^^^
    |
    = note: `clap` is in scope, but it is a crate, not an attribute

error[E0599]: no function or associated item named `parse` found for struct `Options` in the current scope
   --> /home/monkatraz/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:147:14
    |
10  | struct Options {
    | -------------- function or associated item `parse` not found for this
...
147 |     Options::parse().execute();
    |              ^^^^^ function or associated item not found in `Options`
    |
    = help: items from traits can only be used if the trait is implemented and in scope
    = note: the following traits define an item `parse`, perhaps you need to implement one of them:
            candidate #1: `StructOpt`
            candidate #2: `mrml::prelude::parse::Parser`
            candidate #3: `Parsable`

Some errors have detailed explanations: E0432, E0599.
For more information about an error, try `rustc --explain E0432`.
error: failed to compile `mrml-cli v1.3.2`, intermediate artifacts can be found at `/tmp/cargo-installNJHxMH`

Caused by:
  could not compile `mrml-cli` due to 16 previous errors

Here are my cargo and rustc versions, if that is of any help:

cargo 1.57.0 (b2e52d7ca 2021-10-21)
rustc 1.57.0 (f1edd0429 2021-11-29)

Allow HTML boolean attributes

The following example, using a boolean attribute on the <ol> tag (see: docs at MDN and WHATWG ), does not work:

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text>
          <ol reversed>
            <li>Second</li>
            <li>First</li>
          </ol>
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

Boolean attributes can be expressed in some other ways, to work around this, the following work:

 <ol reversed="">
 <ol reversed="reversed">

and these don't:

 <ol reversed>
 <ol reversed=reversed>

The former are valid XML the latter not, which may explain things (see: #221)

This is also the case in <mj-raw> tags, which is where I am actually trying to use a tag with a boolean attribute. I can obviously work around it, but this behaviour is inconsistent with the mjml.io implementation.

Inside table tag <img> Tag is not working

<mjml lang="en"> <mj-body> <mj-section> <mj-column> <mj-table> <tr> <td> <img src="https://picsum.photos/200/300"> </img> </td> </tr> <tr> <td> <img src="https://picsum.photos/200/300"> </img> </td> </tr> <tr> <td> <img src="https://picsum.photos/200/300"> </img> </td> </tr> <tr> <td> <img src="https://picsum.photos/200/300"> </img> </td> </tr> <tr> <td> <img src="https://picsum.photos/200/300"> </img> </td> </tr> </mj-table> </mj-column> </mj-section> </mj-body> </mjml>

Screenshot 2022-12-06 at 4 19 40 PM

Is for loop supported ?

Hi,

I've tried something like:

        <mj-raw>{% for resource in resources %}</mj-raw>
        <mj-text>
                Ressource: {{ resource }}
        </mj-text>
        <mj-raw>{% endfor %}</mj-raw>

then:

curl -X POST -v \ 
  -H "Content-Type: application/json" \
  --data '{"from":"EMAIL","to":"EMAIL","params":{"name": "Edouard", "resources":["01","02"], "token":"TOKEN_DATA"}}' \
  http://localhost:3000/templates/test

with no luck. Is there a way to make simple loops in mjml ?

best regards

Support mj-include tag

I would like to be able to reuse template fragments. What do you think about supporting taking in a map of <path, content>, so that mrml wouldn't have to search for the includes at runtime?

use indexmap instead of hashmap for attributes?

First of all, thank you for your hard work on this library! I was excited to see a mjml implementation in rust, it works great and is very fast.

Second, would you be open to using an IndexMap instead of a HashMap?

I am trying to write a test that asserts the output of a MRML template with an expected/known constant value but am running into problems with the ordering of an element's attributes. I believe that using an indexmap would order the attributes based their insertion order, which should allow for rendering a stable output value between MRML instances.

The example below helps illustrate the problem I am running into.

let source = "<mjml><mj-body><mj-section><mj-column><mj-text>hi</mj-text></mj-column></mj-section></mj-body></mjml>";
let options = mrml::prelude::render::Options::default();

let root_1 = mrml::parse(source).unwrap();
let root_2 = mrml::parse(source).unwrap();

let output_1 = root_1.render(&options).unwrap();
let output_2 = root_2.render(&options).unwrap();
// fails
assert_eq!(output_1, output_2);

If you're open to this change, I can try to open a PR in the next couple of days with a possible solution.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.