Trimming Down jQuery With Grunt

I’ve previously written about using jQuery’s AMD modules to include only the pieces of jQuery you need. That approach relied on explicitly listing jQuery modules, i.e. whitelisting, which works well if you only need a few modules.

The more common case, however, is applications that use numerous jQuery modules. In those applications, micromanaging a list of modules can easily become a maintenance nightmare. In this article I want to discuss the opposite approach: blacklisting, or excluding pieces of jQuery that you don’t need. The blacklisting approach lets you shave off a few bytes, while maintaining the convenience of a single jquery.js file.

In the past several releases, the jQuery team has reorganized their file structure to make the blacklisting process more effective, by placing less common pieces of functionality in their own files. In this article we’ll look at how to blacklist jQuery modules, which modules you should remove, and how to automate the whole thing.

Building Your Own jQuery

In version 1.8, the jQuery team introduced a Grunt task for building custom versions of jQuery. For example, the following clones jQuery’s git repository, installs its dependencies, and runs a custom build that excludes all of jQuery’s Ajax handling.

$ git clone git@github.com:jquery/jquery.git
$ cd jquery
$ npm install
$ grunt custom:-ajax

This depends on having the Grunt CLI installed, which you can do with npm install -g grunt-cli. For details see http://gruntjs.com/getting-started.

The custom Grunt task places the built files in the repository’s dist folder; you can go there to see how big of a difference the build made. In the case of the example above, removing the ajax module reduced jquery.min.js from 82K (29K gzipped), to 73K (25K gzipped) — a ~14% savings in the gzipped size.

To exclude multiple modules when using the grunt custom task, append each module to the end of the task using , as a delimiter. For example the following performs a build of jQuery without the ajax and css modules: grunt custom:-ajax,-css.

So… how do you actually use this in your app? Basically, the intended workflow is, determine a list of modules you don’t need, pass them to jQuery’s grunt custom task, and then copy the new version of jQuery back into your project. The hard parts here are determining which modules to exclude, and figuring out how to automate the process. Let’s discuss each.

Good jQuery Modules to Blacklist

As there are numerous modules in jQuery, identifying modules you can blacklist can be a tricky task. The following is a list of modules that I have found to be good candidates for exclusion.

The file size numbers I use in this article are based on version 2.1.1 of jQuery, which is 84,280 bytes, and 29,577 bytes gzipped.

core/ready

If I had a nickel for every time I wrote $( document ).ready( ... )… I’d probably have like 20 bucks. Although ready() used to be one of the coolest methods in jQuery, the web has adopted a new best practice of placing scripts right before the </body> tag. And if you do that, you have no need for ready(), as the DOM is already in place when your JavaScript runs.

Excluding the core/ready module removes about ~1/2 a kilobyte of JavaScript and enforces the best practice of placing your scripts at the bottom of your web applications.

Still confused? Burke Holland gives a detailed explanation of the problem with document ready in his 5 Things You Should Stop Doing With jQuery article (stop using document ready is #1).

  • Run: grunt custom:-core/ready
  • Save: 638 bytes, 206 bytes gzipped

effects

jQuery effects revolutionized animations on the web. A complex set of setInterval() calls transformed into elegant APIs like animate(), fadeIn(), and fadeOut(). But the web has come a long way since then. You can now do more performant animations with CSS in all modern browsers. Chrome 36 even includes a native animate() method that runs as efficiently as CSS-based animations (and that uses a jQuery-inspired API).

Until the native animate() becomes widely available, you can alternatively use performance-minded animation libraries — such as Velocity.js and jQuery Transit — which provide jQuery animation APIs, but use CSS-based animations under the hood.

If you have switched to CSS-based animations, or are using a replacement animation library, you can exclude the effects module and save ~7K.

  • Run: grunt custom:-effects
  • Save: 7428 bytes, 2635 bytes gzipped

deprecated

jQuery relegates deprecated APIs into the deprecated module for easy blacklisting. Although this module contains only one method — andSelf() — it’s a good idea to remove this module to ensure you’re not using deprecated APIs.

  • Run: grunt custom:-deprecated
  • Save: 43 bytes, 15 bytes gzipped

ajax/script

Do you use jQuery to perform Ajax calls that retrieve JavaScript files and execute them? If not, exclude the ajax/script module to trim jQuery’s file size.

  • Run: grunt custom:-ajax/script
  • Save: 602 bytes, 180 bytes gzipped

ajax/jsonp

Do you use JSONP Ajax APIs? If not, exclude the ajax/jsonp module to save a few bytes.

  • Run: grunt custom:-ajax/jsonp
  • Save: 823 bytes, 280 bytes gzipped

event/alias

jQuery provides a number of shorthand methods to listen for specific DOM events, such as change(), click(), and focus(). Some people prefer the shorthands, and some people prefer using the on() method for everything. For instance the following two lines of code do the same thing:

$( "input" ).focus( ... );
$( "input" ).on( "focus", ... );

If you prefer the on() signature, you can exclude the event/alias module to remove the shorthand methods.

  • Run: grunt custom:-event/alias
  • Save: 618 bytes, 200 bytes gzipped

wrap

The wrap() method wraps selected elements in the provided HTML structure, and the unwrap() method does the opposite. For example the following wraps all <p> elements with a new <div>:

$( "p" ).wrap( "<div>" );

Although wrap() and unwrap() are handy when you need them, if you don’t, you can exclude the wrap module.

  • Run: grunt custom:-wrap
  • Save: 728 bytes, 178 bytes gzipped

What else?

The list above represents a set of easy targets, or modules that most people will be able to exclude. You can view a more complete list of modules, as well as a list of what methods each modules contains in my previous article on jQuery’s AMD modules. jQuery’s GitHub repo also contains a small list of modules that are good candidates for exclusion.

Will this make a difference?

As with any programming advice, your mileage may vary — that is, how much an optimization like this matters depends on your projects, how important optimizing file size is for them, and how much of jQuery they use. Performing a build that excludes all modules listed in this article, or grunt custom:-core/ready,-effects,-deprecated,-ajax/script,-ajax/jsonp,-event/alias,-wrap, results in a jQuery file that is 71.6K and 25.2K gzipped — a savings of 10.7K and 3.6K gzipped. That isn’t a whole lot, but your applications will likely be able to add additional modules for further savings.

Any optimization is usually worth doing if you can do it seamlessly, but as is, the process described so far in this article is a lot of manual work. Let’s see how you can make that easier.

How Do I Automate This?

As a developer, I avoid manual work like an Objective C developer avoids modern programming constructs. Running the grunt custom task requires me to run four or five commands — which is way too much work for a lazy developer like me. More importantly, I want someplace in my codebase that I can store the list of modules that I want to exclude — i.e. my project’s blacklist — otherwise I’ll forgot my list within hours.

This has come up before, and someone in the jQuery community created an online builder for jQuery. However, although the online builder provides a nice UI, it only allows you to exclude from a hardcoded list of modules, and it doesn’t support the latest versions of jQuery. Plus, even if the builder were up to date, going out to use a website is still manual work that I don’t want to do.

There are also two existing Grunt tasks — grunt-jquerybuilder and grunt-jquery-builder — but both are built on top of the npm module that drives the online builder, and are both subject to the same restrictions we just discussed.

But don’t give up hope, it’s times like this that I like to bust out my favorite automation power tool: grunt-shell.

Using grunt-shell to automate anything

Almost anything you do on a computer can be automated with a shell script, but, unless you have an extensive Linux administration background, the nuanced syntax of shell script tends to be obscenely difficult to work with — I’ve certainly had plenty of frustrating experiences at the very least.

But for JavaScript developers dealing with the shell is becoming far easier because of several recent projects. First there’s Node, which has given us JavaScript APIs for low-level tasks, such as processes. Then there are task runners — such as Grunt and Gulp — which build on Node, and offer elegant APIs and plugins for automation problems of all sorts.

I find one Grunt plugin, grunt-shell, particularly useful as it gives you a simple API to run shell commands. For example here’s a silly Gruntfile that defines a list task that lists the contents of the current directory:

module.exports = function( grunt ) {
    "use strict";

    grunt.initConfig({
        shell: {
            list: {
                command: "ls"
            }
        }
    });

    grunt.loadNpmTasks( "grunt-shell" );
};

You can run this task with grunt shell:list. To expand on this concept, and to return to the problem of building jQuery, here’s a Gruntfile that automates the process of building jQuery:

module.exports = function( grunt ) {
    "use strict";

    // The version of jQuery to build
    var version = "2.1.1",

        // An array of jQuery modules to exclude
        exclude = [ "core/ready", "effects", "deprecated", "ajax/script",
            "ajax/jsonp", "event/alias", "wrap" ],

        // The destination and filename of the built jQuery file
        dest = "jquery-built.js"

    exclude.forEach(function( module, index ) {
        exclude[ index ] = "-" + module;
    });

    grunt.initConfig({
        shell: {
            jquery: {
                command: [
                    "git clone https://github.com/jquery/jquery.git",
                    "cd jquery",
                    "git checkout " + version,
                    "npm install",
                    "grunt custom:" + exclude.join( "," ),
                    "cd ../",
                    "cp jquery/dist/jquery.js " + dest,
                    "rm -rf jquery"
                ].join( "&&" )
            }
        }
    });

    grunt.loadNpmTasks( "grunt-shell" );
};

Replace the version, exclude, and dest variables that fit your application, and then run grunt shell:jquery to build your custom version of jQuery.

You may be thinking, “Wait, is this cloning jQuery’s repository, installing its dependencies, and running a grunt custom build every time? Won’t that take a while?” Yes, yes it does.

I’m not saying this is necessarily the best way to write this — because it certainly isn’t — but it does work, even on Windows (provided you use a bash emulator such as Git BASH). And you only need to run this anytime your module needs change, or when you need to update jQuery versions.

I offer this as a suggestion for how you might accomplish something like this, as well as to show how easy grunt-shell makes automating shell-based tasks. For those of you that do have extensive scripting experience — shell, bash, Windows, Node, Grunt, Gulp, Ant, Rake, Make, whatever — I’m curious how you would automate this. If you have a more elegant solution, please post it in the comments.

Wrapping Up

If you work on an application that uses jQuery, and performance is critical, you can perform a custom build of jQuery to trim its file size. For certain modules, such as core/ready and effects, excluding jQuery modules can enforce web best practices — such as placing scripts before the </body> tag and using CSS-based animations — as well as reduce the number of bytes your users download.

Very few applications use all of jQuery, and most can find a few modules to exclude to save on file size. If you automate the process using a tool like grunt-shell, you can maintain your blacklist of modules as new versions of jQuery are released.

Comments

  • David Tang

    Thanks! I was just doing this the past few days. Do you happen to know the differences between ajax module and the ajax/xhr module?

    • It is the XMLHTTPRequest ajaxTransport (http://api.jquery.com/jQuery.ajaxTransport/). I’m not that familiar with this portion of the library, so I’m actually not sure on the details, but I’ve always found that I need the ajax/xhr module.

      • David Tang

        You’ve found that you needed ajax/xhr in order for $.ajax to work? or something ajax related and you’re not sure what part?

        • Hey @disqus_EWGH0IH1qN:disqus, sorry for the late response. I have found that I need ajax/xhr in order for $.ajax to work with basic XHR requests, although I can’t explain the technical details about why that is.

  • andrewvijay

    Great article read today!! Thanks TJ

    • No problem. I’m glad you enjoyed it.

  • Pingback: BibSonomy :: publication :: Trimming Down jQuery With Grunt()

  • Pingback: Trimming Down jQuery With Grunt | JavaScript fo...()

  • Pingback: Trimming Down jQuery with Grunt - | Modern web ...()

  • Pingback: Trimming Down jQuery With Grunt | Javascript | ...()

  • Juan David Pastás Rivera

    nice, it makes me think that everything needs a package manager logic, even when you think a package is just a package. Thanks for your article.

    Also, I think this kind of compressions should not be needed if a site uses caching to return an empty response. Right?

  • Nice article. Thak you!

  • Matt

    If only I could do this with Gulp.