Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,12 @@ V2 is a drop in replacement for https://github.com/wix-incubator/mjml-react/, wi

## What's new in V3?

We wanted V3 of mjml-react to be fairly easy to migrate to from V2. We will implement more advanced features in V4. The main updates in V3 include:
We wanted V3 of mjml-react to be fairly easy to migrate to from V2.

- Typescript: mjml-react is now written in typescript which helps ensure correct props are passed to mjml components
- Full mjml component support: We use an automated script for pulling mjml components and creating a corresponding mjml-react component. This means we get full support of all components available in the latest mjml version
- Other small changes: add dangerouslySetInnerHTML in mjml-react for mjml ending tags, update testing, add in-code documenation

## What's coming in V4?

In V4 we are exploring exciting features that will make mjml-react even more powerful. This includes:

- Improved prop type safety: help ensure correct formatting for props like padding, width, and height

If you want to be on the cutting edge of what is being released, we are publishing a [v4-main-alpha version](https://www.npmjs.com/package/@faire/mjml-react/v/main-alpha) to npm.

## Getting Started

1. Choose between rendering in mjml or mjml-browser based on your rendering environment. Also includes additional required dependencies.
Expand All @@ -64,7 +56,9 @@ import { renderToMjml } from "@faire/mjml-react/utils/renderToMjml";
import { MJMLParseResults } from "mjml-core";
import React from "react";

export function renderReactToMjml(email: React.ReactElement): MJMLParseResults {
export function renderReactToMjml(
email: React.ReactElement
): Promise<MJMLParseResults> {
return mjml2html(renderToMjml(email));
}
```
Expand All @@ -86,7 +80,7 @@ import {

import { renderReactToMjml } from "./renderReactToMjml";

const { html, errors } = renderReactToMjml(
const { html, errors } = await renderReactToMjml(
<Mjml>
<MjmlHead>
<MjmlTitle>Last Minute Offer</MjmlTitle>
Expand Down Expand Up @@ -127,7 +121,13 @@ And as the result you will get a nice looking email HTML (works in mobile too!)
{
keepComments: false,
beautify: false,
minify: true,
minify: false,
minifyOptions: {
collapseWhitespace: true,
minifyCss: true,
removeComments: "safe",
removeEmptyAttributes: true,
},
validationLevel: 'strict'
}
```
Expand Down
2 changes: 1 addition & 1 deletion jest-dist.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import baseConfig from "./jest.config.mjs";
export default {
...baseConfig,
moduleNameMapper: {
"src(.*)$": "<rootDir>/dist$1",
"\\.\\./src(.*)$": "<rootDir>/dist$1",
},
};
11 changes: 5 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"build-esm": "tsc -p tsconfig-esm.json",
"lint": "eslint .",
"format": "prettier --write .",
"test": "jest",
"test-dist-skip-build": "jest --config=jest-dist.config.mjs",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without --experimental-vm-modules Jest seemingly explodes due to a dependency on prettier@3.0.0 by mjml-core

prettier/prettier#15769 (I tried upgrading to Jest v30 and it didnt fix the issue)

"test-dist-skip-build": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config=jest-dist.config.mjs",
"test-dist": "yarn build-dist && yarn test-dist-skip-build",
"generate-mjml-react": "ts-node scripts/generate-mjml-react.ts",
"build-mjml-react": "ts-node scripts/build-mjml-react.ts",
Expand All @@ -45,12 +45,11 @@
"@semantic-release/github": "^12.0.0",
"@semantic-release/npm": "^13.1.5",
"@trivago/prettier-plugin-sort-imports": "^3.2.0",
"@types/html-minifier": "^4.0.2",
"@types/jest": "^29.5.14",
"@types/lodash.camelcase": "^4.3.9",
"@types/lodash.kebabcase": "^4.1.9",
"@types/lodash.upperfirst": "^4.3.9",
"@types/mjml": "^4.7.0",
"@types/mjml": "^5.0.0",
"@types/node": "^22.19.0",
"@types/react": "^18.3",
"@types/react-dom": "^18.3",
Expand All @@ -68,7 +67,7 @@
"jest": "^29.7.0",
"lodash.camelcase": "^4.3.0",
"lodash.upperfirst": "^4.3.1",
"mjml": "^4.15.3",
"mjml": "^5.0.1",
"prettier": "^2.2.1",
"pretty-quick": "^3.1.3",
"react": "^18.3",
Expand All @@ -79,7 +78,7 @@
"typescript": "5.9.3"
},
"peerDependencies": {
"mjml": "^4.13.0",
"mjml": "^5.0.0",
"react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
Expand Down
1 change: 1 addition & 0 deletions src/mjml/MjmlAccordionTitle.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/mjml/MjmlBody.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/mjml/MjmlCarousel.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/mjml/MjmlCarouselImage.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/mjml/MjmlColumn.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion src/mjml/MjmlHero.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/mjml/MjmlSocialElement.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/mjml/MjmlWrapper.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 8 additions & 18 deletions src/utils/render.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { minify as htmlMinify } from "html-minifier";
import mjml2html from "mjml";
import { MJMLParsingOptions, MJMLJsonObject, MJMLParseError } from "mjml-core";
import React from "react";
Expand All @@ -14,30 +13,21 @@ interface ConvertedHtml {
export function render(
email: React.ReactElement,
options: MJMLParsingOptions = {}
): ConvertedHtml {
): Promise<ConvertedHtml> {
const defaults: MJMLParsingOptions = {
keepComments: false,
beautify: false,
validationLevel: "strict",
minifyOptions: {
collapseWhitespace: true,
minifyCss: true,
removeComments: "safe",
removeEmptyAttributes: true,
},
};

const parseResults: ConvertedHtml = mjml2html(renderToMjml(email), {
return mjml2html(renderToMjml(email), {
...defaults,
...options,
minify: undefined,
});

if (options.minify) {
return {
html: htmlMinify(parseResults.html, {
caseSensitive: true,
collapseWhitespace: true,
minifyCSS: true,
removeComments: true,
removeEmptyAttributes: true,
}),
};
}

return parseResults;
}
16 changes: 8 additions & 8 deletions test/render.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import { render } from "../src/utils/render";

describe("render()", () => {
it("should render the HTML", () => {
it("should render the HTML", async () => {
const email = (
<Mjml>
<MjmlHead>
Expand All @@ -28,35 +28,35 @@ describe("render()", () => {
</MjmlBody>
</Mjml>
);
const { html } = render(email, { minify: true });
const { html } = await render(email, { minify: true });
expect(html).toBeDefined();
expect(html).toContain("<!doctype html>");
expect(html).toContain("<title>Title</title>");
expect(html).toContain("<p>Paragraph</p>");
});

it("should throw an error if invalid markup is given", () => {
it("should throw an error if invalid markup is given", async () => {
const email = (
<Mjml>
<MjmlBody>
<div>Ooops!</div>
</MjmlBody>
</Mjml>
);
expect(() => render(email)).toThrow(
await expect(() => render(email)).rejects.toThrow(
"Element div doesn't exist or is not registered"
);
});

it("should not throw an error if soft validation level is passed", () => {
it("should not throw an error if soft validation level is passed", async () => {
const email = (
<Mjml>
<MjmlBody>
<div>Ooops!</div>
</MjmlBody>
</Mjml>
);
const { errors } = render(email, {
const { errors } = await render(email, {
validationLevel: "soft",
minify: false,
});
Expand All @@ -66,7 +66,7 @@ describe("render()", () => {
);
});

it("should handle all prop types (numbers, booleans, strings, objects, etc.) without error", () => {
it("should handle all prop types (numbers, booleans, strings, objects, etc.) without error", async () => {
const email = (
<Mjml>
<MjmlBody>
Expand All @@ -88,7 +88,7 @@ describe("render()", () => {
</MjmlBody>
</Mjml>
);
const { html, errors } = render(email);
const { html, errors } = await render(email);
// Verify no errors when rendering the different prop types
expect(errors).toHaveLength(0);
expect(html).toBeDefined();
Expand Down
2 changes: 1 addition & 1 deletion tsconfig-eslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": "./tsconfig-base.json",
"compilerOptions": {
"module": "CommonJS",
"target": "es2015",
"target": "es2020",
"outDir": "dist",
"declaration": true
},
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": "./tsconfig-base.json",
"compilerOptions": {
"module": "CommonJS",
"target": "es2015",
"target": "es2020",
"outDir": "dist",
"declaration": true
}
Expand Down
Loading
Loading