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
Follow the steps, using syntax-highlight
as the folder name.
Once it finishes installing dependencies, cd syntax-highlight
and open it in your editor.
We'll be covering two different techniques:
- with a PostHTML plugin
- with Markdown + PrismJS
PostHTML
Using a PostHTML plugin, we can write our own <pre><code>
markup and have the plugin highlight the contents of the <code>
element.
Install plugin
First, let's install the posthtml-prism
plugin, which we'll use to automatically highlight our code blocks:
npm i posthtml-prism
Next, make sure it's used in our build process:
// config.js
module.exports = {
build: {
posthtml: {
plugins: [
require('posthtml-prism')(),
],
},
}
}
Add code block
Add a block of code in your template, like so:
<pre>
<code class="language-javascript">
function foo(bar) {
var a = 42,
b = 'Prism';
return a + bar(b);
}
</code>
</pre>
language-javascript
class on the <code>
tag - this is required in order to get language-specific syntax highlighting.<pre>
tag yourself - see the posthtml example in the tutorial repository.Markdown
Alternatively, we can also use Markdown to write fenced code blocks and have PrismJS automatically syntax-highlight them.
Install PrismJS
First, we must install PrismJS:
npm i prismjs
Configure Markdown
Next, we need to configure Maizzle to use PrismJS as a custom highlight function for the Markdown renderer.
We do that in config.js
:
const Prism = require('prismjs')
module.exports = {
markdown: {
markdownit: {
highlight: (code, lang) => {
lang = lang || 'markup'
return Prism.highlight(code, Prism.languages[lang], lang)
},
}
},
// ...
}
Fenced code block
We can now write code inside a fenced code block in our Template:
<extends src="src/layouts/master.html">
<block name="template">
<md>
```js
function foo(bar) {
var a = 42,
b = 'Prism';
return a + bar(b);
}
```
</md>
</block>
</extends>
Build
Now run maizzle serve
to start the development server, open http://localhost:3000/
in a browser, and navigate to the template.
Whatever technique you chose, you'll see something like this:
function foo(bar) {
var a = 42,
b = 'Prism';
return a + bar(b);
}
If you view the source, you'll notice a lot of <span>
tags. This means it worked, and PrismJS has tokenized our code block.
But it's not very pretty, is it?
Theming
We need a PrismJS theme to make the code block pretty. Choose one of the default themes, or see prism-themes for more.
For this tutorial, we'll go with a Tailwind adaptation the Synthwave '84 Theme.
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";
Next, we need to make sure that our Prism theme CSS is not purged at build time.
We can do this by wrapping its contents in special comments for PurgeCSS:
/*! purgecss start ignore */
/* contents of the prism-synthwave84.css file here... */
/*! purgecss start ignore */
Now, running maizzle build
will yield the result we expected:
function foo(bar) {
var a = 42,
b = 'Prism';
return a + bar(b);
}
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
:
@media screen {
pre {
@apply whitespace-pre !important;
}
}
Outlook
Padding on <pre>
doesn't work in Outlook.
We can fix this by wrapping <pre>
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]-->
<pre>
<code class="language-javascript">
function foo(bar) {
var a = 42,
b = 'Prism';
return a + bar(b);
}
</code>
</pre>
<!--[if mso]></td></tr></table><![endif]-->
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 template inside the build_production/
directory for sending.
Resources
- Repository for this tutorial
- posthtml-prism plugin
- PrismJS library
- Synthwave '84 theme
- Testing: Email on Acid, SendTest.Email