With the ever-increasing importance of mobile, performance on the web has never been more critical. Because of its popularity, jQuery is often targeted as too big because of its size. Although I've argued in the past that the complaints about jQuery's size are overstated, it's nevertheless important to include only the code you need.
The good news is, as of jQuery 2.1, jQuery uses AMD to organize its dependencies internally. This means you can use AMD to load individual pieces of jQuery, and not the whole library. In this article you'll see which jQuery modules are available, and how to use them in an AMD context. For each, I'll show how many bytes you save by using an AMD approach. Finally, we'll look at how to write jQuery plugins that leverage these new modules.
To use the new modules, you need an AMD-ready project. I'll quickly walk you through how to build one with Bower and RequireJS. If you're already comfortable with these technologies, and how to setup a project using them, you may want to skip straight to the modules.
Start by creating a new directory to run these examples in:
$ mkdir jquery-snippets
$ cd jquery-snippets
Then use Bower to install jQuery and RequireJS:
$ bower install jquery
$ bower install requirejs
If you don't have Bower you can install it using
npm install -g bower
. Alternatively, you can manually download the files from their respective GitHub repositories. The key is to have jQuery's individual source files available, and not a singlejquery.js
file.
With the libraries in place, create an index.html
that looks like this:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery Snippets</title>
</head>
<body>
<script src="bower_components/requirejs/require.js"></script>
<script>
require.config({
paths: {
"jquery": "bower_components/jquery/src",
"sizzle": "bower_components/jquery/src/sizzle/dist/sizzle"
}
});
require([ "app" ]);
</script>
</body>
</html>
The require.config
call tells RequireJS where it can find find the "jquery" and "sizzle" dependencies — which are strings that jQuery uses internally. The require([ "app" ])
call asynchronously loads app.js
— which is the file you'll be placing your code in. Go ahead and create app.js
as an empty file for now. You should end up with a directory structure that looks like this:
.
├── app.js
├── bower_components
│ ├── jquery
│ │ └── src
│ │ ├── ...
│ │ ├── core.js
│ │ ├── ...
│ │ ├── sizzle
│ │ │ └── dist
│ │ │ ├── ...
│ │ │ └── sizzle.js
│ │ └── ...
│ └── requirejs
│ ├── ...
│ └── require.js
└── index.html
The following code snippets work if they are used as the contents of app.js
in the structure above. For each snippet I'll provide the size of jQuery using the dependencies specified. You can compare the specified sizes to the overall size of jQuery 2.1.1, which is 82K (29K gzipped).
Keep in mind that the sizes of the snippets are not additive because some dependencies are shared. For example document ready requires 11K, and Ajax requires 13K (both gzipped), but their combined size is 14K gzipped, not 24K.
The "jquery/core"
module gives you the base of jQuery. It defines the jQuery
object which all methods are placed on, as well as $.fn
(where plugin methods are placed). "jquery/core"
also provides a number of jQuery's utility methods. For example, the following code uses $.each()
:
define([ "jquery/core" ], function( $ ) {
$.each([ 1, 2, 3 ], function( index, number ) {
console.log( number );
});
});
"jquery/core"
.first()
, last()
, end()
, eq()
, get()
, globalEval()
, grep()
, inArray()
, isArray()
, isEmptyObject()
, isFunction()
, isNumeric()
, isPlainObject()
, isWindow()
, map()
, makeArray()
, merge()
, now()
, proxy()
, slice()
, toArray()
, trim()
, and type()
.The "jquery/core/init"
module provides $.fn.init
, or the ability to select DOM elements by passing strings to the $
object. This modules also brings in Sizzle, jQuery's selector engine. The following code uses the $
object to select all <div>
elements:
define([ "jquery/core/init" ], function( $ ) {
console.log( $( "div" ) );
});
define([ "jquery/ajax", "jquery/ajax/xhr" ], function( $ ) {
$.ajax({
url: "https://api.github.com/repos/telerik/kendo-ui-core/commits",
global: false
});
});
global
flag to false
because firing global events requires the jQuery eventing system ("jquery/event"
) — which adds some size. The "jquery/event"
dependency has been removed on the project's master branch, but that change has yet to be included in a release.$.getJSON()
, $.getScript()
, $.get()
, and $.post()
.define([ "jquery/core/init", "jquery/attributes/attr" ], function( $ ) {
$( "div" ).attr( "data-foo", "bar" );
});
removeAttr()
method.define([ "jquery/core/init", "jquery/attributes/classes" ], function( $ ) {
$( "div" ).addClass( "foo" );
});
removeClass()
, toggleClass()
, and hasClass()
methods.define([ "jquery/css" ], function( $ ) {
$( "div" ).css( "color", "red" );
});
show()
, hide()
, and toggle()
methods.define([ "jquery/core/init", "jquery/data" ], function( $ ) {
$( "div" ).data( "foo", "bar" );
});
removeData()
method.define([ "jquery/deferred" ], function( $ ) {
var deferred = $.Deferred();
deferred.then(function() {
console.log( "Done!" );
});
deferred.resolve();
});
define([ "jquery/dimensions" ], function( $ ) {
$( "div" ).height( 500 );
});
width()
, innerHeight()
, innerWidth()
, outerHeight()
, and outerWidth()
methods.define([ "jquery/core/init", "jquery/core/ready" ], function( $ ) {
$(function() {
console.log( "ready!" );
})
});
define([ "jquery/effects" ], function( $ ) {
$( "div" ).hide();
});
fadeTo()
, animate()
, stop()
, finish()
, slideDown()
, slideUp()
, slideToggle()
, fadeIn()
, fadeOut()
, and fadeToggle()
methods, as well as the animation-aware versions of toggle()
, show()
, and hide()
.define([ "jquery/event" ], function( $ ) {
$( "div" ).on( "click", function() {
console.log( "click!" );
});
});
one()
, off()
, trigger()
, and triggerHandler()
methods, as well as the jQuery special events system.define([ "jquery/core/init", "jquery/traversing/findFilter" ], function( $ ) {
console.log( $( "div" ).find( "span" ) );
});
filter()
, not()
, and is()
methods.define([ "jquery/manipulation" ], function( $ ) {
$( "div" ).append( "Hello world" );
});
clone()
, text()
, append()
, prepend()
, before()
, after()
, remove()
, empty()
, html()
, replaceWith()
, detach()
, appendTo()
, prependTo()
, insertBefore()
, insertAfter()
, and replaceAll()
methods.define([ "jquery/offset" ], function( $ ) {
$( "body" ).scrollTop( 1000 );
});
offset()
, position()
, offsetParent()
, and scrollLeft()
methods.define([ "jquery/core", "jquery/core/parseHTML" ], function( $ ) {
$( "<marquee>jQuery!</marquee>" ).appendTo( "body" );
});
define([ "jquery/core/init", "jquery/attributes/prop" ], function( $ ) {
$( "input[type=checkbox]" ).prop( "checked", true );
});
removeProp()
method.define([ "jquery/traversing" ], function( $ ) {
$( "img" ).closest( "div" );
});
has()
, closest()
, index()
, add()
, addBack()
, parent()
, parents()
, parentsUntil()
, next()
, prev()
, nextAll()
, prevAll()
, nextUntil()
, prevUntil()
, siblings()
, children()
, and contents()
methods.define([ "jquery/core/init", "jquery/attributes/val" ], function( $ ) {
$( "input" ).val( "hello world" );
});
define([ "jquery/manipulation", "jquery/wrap" ], function( $ ) {
$( "div" ).wrap( document.createElement( "div" ) );
});
"jquery/manipulation"
dependency has been removed on the project's master branch, but for now you have to explicitly declare it.Sure! jQuery was separated into modules to encourage usage of these subcomponents. If you're extremely concerned about bytes, and you only need a part of jQuery, only declare dependencies on the pieces you need.
If you don't like explicitly listing your jQuery module dependencies, you can also take the opposite approach, and exclude the parts of jQuery you don't need. See jQuery's README for documentation on how to build a version of jQuery that blacklists the modules you don't need.
Although it's easy to pick an approach that works for you in your own projects, things get trickier with distributable code — e.g. libraries, frameworks, and plugins. Let's look at how you can use these jQuery modules in code you intend to share with others, by discussing a new pattern for building jQuery plugins.
jQuery plugin patterns used to be the hip thing to write about, but that's no longer the case. Why? Well, it's not because jQuery's usage has decreased — because that has never been higher. Instead, it's because the question of "how to write a jQuery plugin" has been answered. In my opinion, Addy Osmani had the last word with "Essential jQuery Plugin Patterns" — an extraordinarily comprehensive article that aggregated existing patterns with advanced usage scenarios. But with the inclusion of AMD in jQuery, coupled with the ever-increasing importance of shipping a lightweight payload to mobile devices, it's time for a new pattern.
To build one, let's start with a define()
call that declares your jQuery dependencies, and adds a single pluginName()
method to $.fn
:
define([ "jquery/foo", "jquery/bar", ... ], function( $ ) {
$.fn.pluginName = function() {
...
return this;
};
return $;
});
"jquery/foo"
and "jquery/bar"
are placeholders for the actual jQuery module dependencies listed above — e.g. "jquery/css"
, "jquery/event"
, and so forth. $.fn
is defined in "jquery/core"
, which is a dependency of all jQuery modules, so it will always be available when you depend on a jQuery module (and it is just 1.9K gzipped).
The return $
line at the end ensures that consumers of this plugin can access it through the traditional jQuery object. For instance, if the above code was in a file named plugin.js
, the plugin could be consumed with this code:
define([ "plugin" ], function( $ ) {
$( "*" ).pluginName();
});
The advantage of this approach is you only require the parts of jQuery that you need. If you're writing a plugin that needs to perform Ajax calls, you don't need to ship code to perform animations.
But there is one major problem with this approach: it only works for AMD users. For better or worse, the vast majority of developers do not use AMD, and they expect plugins to work as long as jQuery is included as a global variable. So if you want anyone to actually use your plugin, you have to make global usage work.
Fortunately, there's a well established solution for writing code that works in AMD and non-AMD environments: UMD, or the Universal Module Definition. In simple terms, the UMD approach works by detecting whether the current code is running in an AMD environment. If it is, you registers the module's object as an AMD module. Otherwise, you register the object as a global variable. James Burke publishes a series of boilerplates for writing UMD modules — including one specifically for jQuery plugins.
Building upon James Burke's boilerplate, and including the jQuery modules above, I present the jQuery UMD module pattern.
(function ( factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module
// Replace "jquery/foo", "jquery/bar", etc with your own jQuery module dependencies.
define([ "jquery/foo", "jquery/bar", ... ], factory );
} else {
// Register as a global variable
factory( jQuery );
}
}(function( $ ) {
$.fn.pluginName = function () {
...
return this;
};
return $;
}));
As a concrete example, here's a (very practical) kittenTime()
plugin that takes the selected elements, finds their <img>
children, and changes them to kitten images with random dimensions:
(function ( factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define([ "jquery/core", "jquery/core/init", "jquery/traversing/findFilter" ],
factory );
} else {
// Register as a global variable
factory( jQuery );
}
}(function( $ ) {
$.fn.kittenTime = function() {
return this.find( "img" ).each(function( index, element ) {
this.src = "http://placekitten.com/" +
parseInt( Math.random() * 500 ) +
"/" +
parseInt( Math.random() * 500 )
});
};
return $;
}));
This plugin is a slim 9.4K gzipped, but can still use the $
object — and the underlying Sizzle selector engine — to select elements from the DOM. And because the plugin uses UMD, non-AMD users can still use the plugin in a <script>
tag.
Before you get too excited, there are two caveats to this approach. First, AMD users that consume this plugin must use the individual jQuery source files — not jquery.js
. This goes against developers expectations, even AMD developers expectations, as the usage of a single file for jQuery is ingrained into most developers workflows. Second, because this pattern uses jQuery internal modules, it only works in versions of jQuery that have those modules available — namely 2.1+.
Nevertheless, this plugin pattern offers an appealing way to utilize the parts of jQuery you need without requiring the entire jQuery library. What do you think? Is it time to start thinking about jQuery as several well-defined modules rather than a single file?
TJ VanToll is a frontend developer, author, and a former principal developer advocate for Progress.