How to add syntax highlighting to HTML emails
If you want to show a block of code in an HTML email and have it look nice, it usually involves a lot of manual work: escaping, formatting, tokenizing, styling tokens...
With Maizzle however, we can use JavaScript libraries to do that work for us 💅
Getting started
Let's create a new Maizzle project.
Open a terminal window and run the new
command:
maizzle new syntax-highlight
That will create a syntax-highlight
folder at your current location, clone the Starter in it, and install NPM dependencies for you.
OK, now open the syntax-highlight
folder in your editor.
Markdown
We'll use Markdown to add our code block to the email template.
We could manually add <pre>
and <code>
tags, but with Markdown the code is automatically formatted (escaped) for us.
We can even add our own sanitizer function.
Edit your template and add a {% markdown %}
tag somewhere in your content (anywhere a <pre>
element would be allowed).
We'll be using the Promotional template from Maizzle, so we'll add the markdown block right after the <p>
from the first article:
<p class="m-0 mb-24">For example, here's a block of JavaScript code:</p>
{% markdown %}
```js
function foo(bar) {
var a = 42,
b = 'Prism';
return a + bar(b);
}
```
{% endmarkdown %}
There's a hidden ‌
character in the closing of the fenced code block above (in ```
above {% endmarkdown %}
). This is needed for nesting fenced code blocks on this page. If you're copy-pasting the example above, make sure to delete and replace all ```
with your own.
Now run maizzle serve
to start the development server, and open http://localhost:3000/promotional.html
in a browser.
You'll see something like this:
Not very pretty, is it?
Let's use PrismJS to make it look better.
PrismJS
Install the PrismJS library in your project by running this command in the terminal at your project's root:
npm install prismjs
Theme
We'll also need a PrismJS theme to make the code block pretty. Choose one of the default themes, or see prism-themes for more.
We'll go with the Synthwave '84 Theme, here's how it looks like:
Save prism-synthwave84.css to the src/assets/css/custom
directory in your project,
and import it in your src/assets/css/main.css
:
/* Your custom CSS resets for email */
@import "custom/reset";
/* Tailwind components that are generated by plugins */
@import "tailwindcss/components";
/**
* @import here any custom components - classes that you'd want loaded
* before the Tailwind utilities, so that the utilities could still
* override them.
*/
@import "custom/prism-synthwave84";
/* Tailwind utility classes */
@import "tailwindcss/utilities";
/* Your custom utility classes */
@import "custom/utilities";
We'll get back to the prism-synthwave84.css
file later.
Events
In order to use PrismJS to highlight fenced code blocks in our email template, we'll use Events in Maizzle.
First, we need to require
PrismJS, so edit config.js
:
const Prism = require('prismjs')
module.exports = {
// ...
}
Next, we'll be using the beforeCreate
event, which allows us to programmatically set config options.
Add it inside an events
object, in config.js
:
const Prism = require('prismjs')
module.exports = {
// ...
events: {
async beforeCreate(config) {
config.markdown.highlight = (code, lang, callback) => {
return Prism.highlight(code, Prism.languages[lang], lang)
}
},
},
}
Inside the beforeCreate()
function, we define a highlight
function for the Markdown renderer and have it use PrismJS for highlighting code blocks.
This function must return
the highlighted code as an HTML string - take a look at the marked.js docs for an explanation of all the Markdown renderer options.
If we run maizzle build
again, we see our code looking the same.
Why is it not working?
If you look at the source code, you'll see PrismJS did do something - it tokenized the code, which is just a fancy way of saying it wrapped pieces of text in <span>
tags.
We don't notice any difference, because of CSS purging and clean-up in Maizzle.
Since we're not actually using any of them in our .njk
template file, CSS classes from prism-synthwave84.css
are purged (ignored).
We can fix this by telling the library that does this purging (postcss-purgecss) to ignore this file, by wrapping its contents in comments, like so:
/*! purgecss start ignore */
/* contents of the prism-synthwave84.css file here... */
/*! purgecss start ignore */
Now, running maizzle build
will finally show us we're making some progress:
We're almost there, we just need to customize the Synthwave '84 theme a bit.
With PrismJS, we don't really need any class="language-xxxx"
, so we can remove those selectors from the CSS.
Take a look at the final prism-synthwave84.css
file.
Compatibility
Some email clients require extra steps in order to render our code blocks properly.
Gmail
Gmail will change our inline white-space: pre;
to white-space: pre-wrap;
.
This results in code wrapping, instead of showing a horizontal scrollbar.
Fix it by adding the following CSS at the beginning of prism-synthwave84.css
:
@screen all {
pre {
@apply whitespace-pre !important;
}
}
Outlook
Padding on <pre>
doesn't work in Outlook, and we'll also see a line-height
issue.
We can fix these by wrapping {% markdown %}
tags inside a table that we only show in Outlook. We then style this table inline, like so:
<!--[if mso]><table width="100%"><tr><td style="background: #2a2139; padding: 24px;"><![endif]-->
{% markdown %}
```js
function foo(bar) {
var a = 42,
b = 'Prism';
return a + bar(b);
}
```
{% endmarkdown %}
<!--[if mso]></td></tr></table><![endif]-->
Inline code blocks
We can adjust the Synthwave '84 theme to style inline code
, too.
Change this:
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
to this:
/* Inline code */
:not(pre) > code {
@apply bg-gray-100 border border-solid border-gray-300 text-red-400 text-sm px-8 py-4 rounded whitespace-normal;
}
Production build
We've been working with config.js
until now, which is configured for local development.
This means CSS isn't inlined, and most email optimizations are off.
When you're satisfied with the dev preview, run maizzle build production
and use the build_production/promotional.html
file for sending.
Resources
- Repository for this tutorial
- PrismJS
- Synthwave '84 theme
- Markdown in email
- Maizzle Events
- Testing: Email on Acid, SendTest.Email