Starbeamrainbowlabs

Stardust
Blog


Archive


Mailing List Articles Atom Feed Comments Atom Feed Twitter Reddit Facebook

Tag Cloud

3d 3d printing account algorithms android announcement architecture archives arduino artificial intelligence artix assembly async audio automation backups bash batch blender blog bookmarklet booting bug hunting c sharp c++ challenge chrome os cluster code codepen coding conundrums coding conundrums evolved command line compilers compiling compression containerisation css dailyprogrammer data analysis debugging demystification distributed computing docker documentation downtime electronics email embedded systems encryption es6 features ethics event experiment external first impressions freeside future game github github gist gitlab graphics hardware hardware meetup holiday holidays html html5 html5 canvas infrastructure interfaces internet interoperability io.js jabber jam javascript js bin labs learning library linux lora low level lua maintenance manjaro minetest network networking nibriboard node.js open source operating systems optimisation own your code pepperminty wiki performance phd photos php pixelbot portable privacy problem solving programming problems project projects prolog protocol protocols pseudo 3d python reddit redis reference release releases rendering resource review rust searching secrets security series list server software sorting source code control statistics storage svg systemquery talks technical terminal textures thoughts three thing game three.js tool tutorial tutorials twitter ubuntu university update updates upgrade version control virtual reality virtualisation visual web website windows windows 10 worldeditadditions xmpp xslt

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:

  1. The number of requests made to the server / amount of data transferred
  2. 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

Art by Mythdael