Minifying CSS, HTML, and more in eleventy static sites
I've built a few sites with eleventy, and one of the things that's been on my todo list is figure out a way to optimise everything.
With websites, it's very important that content loads as fast as possible. To achieve this, a number of strategies can be employed, such as enabling gzip to reduce data transferred. A common theme here in techniques used to improve page load times is either:
- The number of requests made to the server / amount of data transferred
- Improving JS / CSS parsing and execution performance
By far the most important thing we can do here with a static site like eleventy though is minifying HTML, CSS, Javascript (if you have any being served to the client), and everything else you serve to the client. In doing so, we can significantly reduce the amount of data transferred from the server to the client.
Build systems like esbuild are a good choice here, but if you have yourself an eleventy-based static site, then esbuild may be somewhat complicated and not best suited to the problem at hand (it's best at bundling JS + CSS assets, and doesn't like HTML very much).
To this end, a solution that is more integrated with eleventy is preferable to reduce complexity. The official eleventy docs suggest using clean-css to minify CSS, but this approach doesn't tackle HTML, and requires you to remember to use the cssmin
filter every time.
With this in mind, in this post I want to show a much easier method of minifying CSS, HTML, and anything else (except non-svg images, but I have a solution for those too which I'll talk about in a future post if there's any interest) you can think of.
By using eleventy transforms, we can apply a minification filter to every file that Eleventy generates.
For this post, I'll assume that you already have an eleventy site you want to optimise. if you don't have one yet, I recommend the official docs as a starting point.
Let's start with the CSS. I assume you already have something like this in e.g. css.njk
in your project:
---
permalink: theme.css
---
{% include "css/patterns.css" %}
{% include "css/theme.css" %}
{% include "css/gallerybox.css" %}
{% include "css/smallscreens.css" %}
{% include "css/prism-custom.css" %}
This puts all your CSS into a single file. This is good, but we can do better. Let's install clean-css
:
npm install --save clean-css
Then, open your .eleventy.js
file for editing. Add the following:
// Add to your require() statements at the top of the file:
const CleanCSS = require("clean-css");
const is_production = typeof process.env.NODE_ENV === "string" && process.env.NODE_ENV === "production";
function do_minifycss(source, output_path) {
if(!output_path.endsWith(".css") || !is_production) return source;
const result = new CleanCSS({
level: 2
}).minify(source).styles.trim();
console.log(`MINIFY ${output_path}`, source.length, `→`, result.length, `(${((1 - (result.length / source.length)) * 100).toFixed(2)}% reduction)`);
return result;
}
Finally, find the bit at the bottom of the file that looks like this:
module.exports = function(eleventyConfig) {
// Some stuff may be here
}
...and add the following to that function there:
eleventyConfig.addTransform("cssmin", do_minifycss);
In short, for every file that eleventy is just about to right to disk, it executes all the transforms it has registered. In our do_minifycss
transform we register, we first ensure it's a .css
file that eleventy is writing, and then check that the NODE_ENV
environment variable is set to production
. If these conditions are met, then we minify the source code we were passed before returning it.
This transform pattern is very useful, and can be applied to any file type you like. For example, we could also minify HTML. To do this, install the html-minifier-terser
npm package like this:
npm install --save html-minifier-terser
Then, here's what to add to the .eleventy.js
configuration file:
// At the top:
const { minify: minify_html } = require("html-minifier-terser");
// Somewhere in the middle:
async function do_minifyhtml(source, output_path) {
if(!output_path.endsWith(".html") || !is_production) return source;
const result = await minify_html(source, {
collapseBooleanAttributes: true,
collapseWhitespace: true,
collapseInlineTagWhitespace: true,
continueOnParseError: true,
decodeEntities: true,
keepClosingSlash: true,
minifyCSS: true,
quoteCharacter: `"`,
removeComments: true,
removeAttributeQuotes: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
sortAttributes: true,
sortClassName: true,
useShortDoctype: true
});
console.log(`MINIFY ${output_path}`, source.length, `→`, result.length, `(${((1 - (result.length / source.length)) * 100).toFixed(2)}% reduction)`);
return result;
}
Finally, add this to the module.exports = function....
at the bottom of the file as before:
eleventyConfig.addTransform("htmlmin", do_minifyhtml);
This follows the same pattern as we did for the CSS, but we instead use the HTML minifier html-minifier-terser
as our minifier instead of the clean-css
CSS minifier.
This pattern is repeatable over and over for other file types. For example, you could use something like JSON.stringify(JSON.parse(source))
to compress pretty-printed JSON, or wrap svgo
to compress SVG images.
If there's a file format, there is probably a minifier for it. Got XML? try minify-xml
. Lua (wow, that's an unusual website you've got there)? try luamin
. PDF? I'm sure there's a minifier / compressor for those too.
Note that if you have a lot of Javascript, esbuild as I mentioned at the beginning of this post may be a better choice. for your Javascript (and potentially CSS).
The reason for this is that esbuild has the ability to tree-shake your Javascript. In other words, it identifies code that you aren't using, and throws it away. This can be very useful if you are using a number of libraries, as these can seriously bloat the size of your final Javascript file.
Conclusion
The larger the site, the more of an effect you'll see by minifying your source code. In this post, I've shown you how to minify your source code in your eleventy sites. Other techniques that you can employ to further reduce load times include:
- Optimising images (I'll write a separate post on this if there's interest, as it can be quite involved)
- Reducing the number of domains the browser has to contact by serving external resources locally from your site
- This avoids the extra latency of setting up a brand-new connection to a new place, since multiple requests to the your own domain can re-use the same connection (and, with HTTP/2 enabled, multiplex multiple requests at once over a single connection)
Hopefully you've found this post useful. If you have, please do leave a comment below.
Have you found a cool minifier or got a cool tip to optimise a static site? Please also share these below too.
Sources and further reading
- esbuild
- eleventy transforms
- Getting started with eleventy
html-minifier-terser
- HTML minifierclena-css
- CSS optimisersvgo
- SVG optimiser