Two Tips To Boost Grunt Performance
Two tips to boost Grunt performance
Akbar S. Ahmed | May 5, 2014

Performance tune Grunt

Grunt is a popular task runner for JavaScript. However, Grunt’s performance can suffer when a large Gruntfile.js uses the same configuration style as a small Gruntfile.js. Your Grunt configuration needs to be adapted as your project grows, much like the other parts of your architecture and infrastructure.

This post is focused on daily Grunt usage during development which including optimizing developer workflows by delivering a faster livereload. This post does not optimize for production builds as production builds are typically run on a CI server and have no impact on developer productivity.

Repo

You can clone and setup the sample Git repo to verify the following performance.

git clone git@github.com:akbarahmed/performance-tune-grunt.git

cd performance-tune-grunt

npm install

Killing performance

Baseline

Let’s start by killing performance. We’ll add a full second to our runtime by doing nothing more than loading all Grunt plugins and running a simple copy.

Our first command is going to give us our performance baseline.

grunt server

With the first command, we’re only loading a few necessary plugins. The time spent loading tasks is 156 ms.

Execution Time (2014-03-07 00:58:53 UTC)
loading tasks  156ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 11%
express:dev    120ms  ▇▇▇▇▇▇▇▇▇▇▇▇ 9%
open:server    202ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 15%

Blindly loading plugins

The easiest way to start using Grunt is to simply load all plugins all the time. In this way, little effort is put into thinking about when a plugin is used.

However, while loading everything is easy, it’s also a performance killer as a Gruntfile.js grows in size.

Let’s run our next command with a Grunt configuration that does nothing more than load all of the packages that we may use in a production configuration file.

grunt --gruntfile Gruntfile-load-all.js server

Notice how we’ve increased the ms for loading tasks from 156 ms to 449 ms. Importantly, this entire increase is due to nothing more than using grunt.loadNpmTasks() to load some plugins (we’re not even doing anything with the plugins).

Execution Time (2014-03-07 01:06:13 UTC)
loading tasks  449ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 16%
express:dev    121ms  ▇▇▇▇▇▇ 4%
open:server    202ms  ▇▇▇▇▇▇▇▇▇▇ 7%

Why does loading plugins slow Grunt?

When Grunt loads a plugin it has to read the package from disk which is a synchronous, blocking operation. Further, some plugins use require() to load additional libraries that the plugin uses in which case you have even more synchronous, blocking operations.

These blocking operations are not noticeable in a small Gruntfile.js, but add to processing time as your Grunt configuration grows.

Finally, let’s slow Grunt down even further. In this next command we’re going to add only one simple task that copies a small number of small text files.

grunt --gruntfile Gruntfile-load-all-with-tasks.js server

With this one small task we have now added 447 ms in processing time. At this point it should be easy to see how by loading more plugins and by running needless tasks we can increase our processing times.

Typically the pain point is somewhere around 6-10 seconds when someone on the team gets fed up with a slow live reload (we have not discussed livereload yet, but I’m assuming you’re reading this article because your reloads are slow…and you’re ready to do something to fix the issue).

Execution Time (2014-03-07 01:17:51 UTC)
loading tasks  453ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 19%
copy:dev       447ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 19%
express:dev    120ms  ▇▇▇▇▇▇▇ 5%
open:server    203ms  ▇▇▇▇▇▇▇▇▇▇▇ 9%

Making Grunt sprint

Optimizing Grunt’s performance involves a few easy changes:

  1. Conditionally load plugins so that only those plugins required for a task are loaded
  2. Use newer to only process files that have changed

First, notice how we nest each grunt.loadNpmTasks() inside of the task that uses the plugin.

Second, we prefix each task that benefits from newer with newer:.

grunt.registerTask('server', [], function() {
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-express-server');
    grunt.loadNpmTasks('grunt-open');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-newer');

    grunt.task.run(
        'newer:copy',
        'express:dev',
        'open',
        'watch'
    );
});

There are of course further optimizations for Grunt, however these two simple changes should significantly reduce Grunt processing time. For example, the default Gruntfile.js used by Exponential now has a less than 1.5 second reload, whereas it previously had an 8 second reload.

Execution Time (2014-03-07 02:09:42 UTC)
    loading tasks  103ms  ▇▇▇▇▇▇▇▇ 5%
    server          73ms  ▇▇▇▇▇▇ 4%
    copy:dev       442ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 22%
    express:dev    119ms  ▇▇▇▇▇▇▇▇▇ 6%
    open:server    204ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 10

That’s it. Let us know how this optimization worked out for you.




Subscribe to our newsletter

Contact Information

ABOUT EXPONENTIAL.IO

We specialize in helping professional developers, like you, expand your skill set. Our courses are focused on enabling you to learn everything necessary to use a new technology in a live, production application.

LOCATION

All courses are made with love in
Palo Alto, CA.

Subscribe to our newsletter