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.
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.
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.
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).
grunt custom:-core/ready
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.
grunt custom:-effects
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.
grunt custom:-deprecated
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.
grunt custom:-ajax/script
Do you use JSONP Ajax APIs? If not, exclude the ajax/jsonp module to save a few bytes.
grunt custom:-ajax/jsonp
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.
grunt custom:-event/alias
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.
grunt custom:-wrap
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.
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.
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.
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.
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.
TJ VanToll is a frontend developer, author, and a former principal developer advocate for Progress.