Compile & Minify CSS in a Hexo Project Using Grunt

Today I learned how to use the Grunt task runner to compile multiple CSS files into one, and minify everything. I did this for a project which uses the Hexo static site generator.

Why Do This?

In previous projects, I’ve pretty much always written one master CSS file for the whole project. It would be insanely long and also pretty cumbersome to work with as the project grew bigger & bigger. Let’s not even think about performance (I didn’t 😅).

Working in some other projects I saw just how self- / user-friendly it is to break code into much smaller pieces, and got into the habit of working with small components across multiple files.

The one area I hadn’t done this yet though was in my CSS. Frameworks like React make it easier for this to be the default way of writing CSS, but otherwise I didn’t know how I could do it. So I looked into some options and settled on using Grunt for the job.

Why Grunt?

I’m actually not 100% positive I made the “right” choice here, but at the end of the day, it does exactly what I need it to do, and I didn’t want to quadruple my research time in order to optimize the decision. Grunt is a task runner that has a well-supported and well-documented module for minifying CSS.

Some other options I considered:

  • Gulp, didn’t choose it after reading this comparison which concludes Grunt is better for simple projects like mine
  • Webpack, seems like absolute overkill for what I need, and I already have a lot of experience with not being able to figure out how to get webpack to work 🥴
  • Manual tools as discussed here, which would have been fine given I don’t need to minify all that often, but it wouldn’t really let me work in multiple files as easily. Plus, why go manual when you can automate?

How To Do It

It turned out to be a really simple setup to add Grunt to my existing Hexo project:

  1. Add Grunt and some Grunt plugins to my project as dev dependencies:

    $ npm install grunt grunt-contrib-cssmin matchdep --save-dev
  2. Add a gruntfile.js to my project root folder and give it some code:

    // /gruntfile.js

    module.exports = function (grunt) {
    require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

    // Project configuration.
    grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    cssmin: {
    src: {
    files: {
    'themes/my-theme/source/css/styles.min.css': [
    'themes/my-theme/source/css/modules/styles.css',
    'themes/my-theme/source/css/modules/test.css'
    ]
    }
    }
    },
    });

    // Default task.
    grunt.registerTask('default', ['cssmin']);
    };
  3. Add a script to my package.json:

    "scripts": {
    "grunt": "grunt", //new
    "build": "hexo generate",
    "clean": "hexo clean",
    "deploy": "hexo deploy",
    "server": "hexo server"
    },
  4. Run npm run grunt and watch the magic happen:

    $ npm run grunt

    > hexo-site@0.0.0 grunt /Users/me/project/my-theme
    > grunt

    Running "cssmin:src" (cssmin) task
    >> 1 file created. 2.33 kB → 2.04 kB

    Done.

This article does a great job breaking down these tasks, and specifically what is happening in the gruntfile.js file. I especially like that they include the matchdep package, which will make it easier to expand this if/when I want to use additional Grunt plugins.

One note though—the article shows how you can insert a banner at the top of your minified CSS…this functionality was removed from Grunt a while ago in case you follow the article and wonder why it doesn’t work!

I also found the GruntJs docs to be helpful.

Testing It

In the above I had two source files compile into the minified CSS file. I tested everything worked correctly by setting a color for everything in each file:

/* File 1  - styles.css */
* {
color: teal;
}

/* File 2 - test.css */
* {
color: orangered;
}

On the page, everything should be the color orangered if the styles are cascading properly. Initially it wasn’t working as expected, until I realized I had the files ordered opposite to what I intended 😂 Order matters!

Some May-Be-Betters

I added the Grunt config file and grunt script at the project’s root level, but if I had any intention of publishing the theme as a standalone, it probably would have been better to add them just to the theme. The scripts would need to be different though.

For now, I also only added grunt as its own script, so I’ll need to manually run it to minify my CSS files each time I change them. In the future I’ll probably just want to amend the Hexo build, generate, and/or deploy scripts so that they run the Grunt tasks before carrying out these commands. Haven’t gotten there yet though.

Old School?

Last thing to note—I have it in my head that Grunt is super old school (well at least in web dev terms 😂) and people have generally moved on to other bundling tools. This could be a completely invalid impression, or I could be right and I’m using something that’s on its way out of fashion.

Regardless, it gets the job done efficiently so it works for me. It actually took less time to set it up than it did to write this how-to recap! So until my needs outgrow this tool, I think I’ll stick with it 🤓