jQuery: Using Only What You Need

jquery_umd_header

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.

The Setup

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 single jquery.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

Modules

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.

Core

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 );
    });
});
  • All other jQuery modules depend on "jquery/core".
  • This module also gives you first(), last(), end(), eq(), get(), globalEval(), grep(), inArray(), isArray(), isEmptyObject(), isFunction(), isNumeric(), isPlainObject(), isWindow(), map(), makeArray(), merge(), now(), proxy(), slice(), toArray(), trim(), and type().
  • Size: 5K, 1.9K gzipped

Initialization

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" ) );
});
  • Size: 26K, 9.4K gzipped

Ajax

define([ "jquery/ajax", "jquery/ajax/xhr" ], function( $ ) {
    $.ajax({
        url: "https://api.github.com/repos/telerik/kendo-ui-core/commits",
        global: false
    });
});
  • I set the 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.
  • This module also gives you $.getJSON(), $.getScript(), $.get(), and $.post().
  • Size: 36K, 13K gzipped

Attributes

define([ "jquery/core/init", "jquery/attributes/attr" ], function( $ ) {
    $( "div" ).attr( "data-foo", "bar" );
});
  • This module also gives you the removeAttr() method.
  • Size: 28K, 10K gzipped

CSS Class Names

define([ "jquery/core/init", "jquery/attributes/classes" ], function( $ ) {
    $( "div" ).addClass( "foo" );
});
  • This module also gives you the removeClass(), toggleClass(), and hasClass() methods.
  • Size: 29K, 10K gzipped

CSS Styles

define([ "jquery/css" ], function( $ ) {
    $( "div" ).css( "color", "red" );
});
  • This module also gives you the show(), hide(), and toggle() methods.
  • Size: 57K, 19K gzipped

Data

define([ "jquery/core/init", "jquery/data" ], function( $ ) {
    $( "div" ).data( "foo", "bar" );
});
  • This module also gives you the removeData() method.
  • Size: 29K, 10K gzipped

Deferreds

define([ "jquery/deferred" ], function( $ ) {
    var deferred = $.Deferred();
    deferred.then(function() {
        console.log( "Done!" );
    });
    deferred.resolve();
});
  • Size: 7.8K, 2.9K gzipped

Dimensions

define([ "jquery/dimensions" ], function( $ ) {
    $( "div" ).height( 500 );
});
  • This module also gives you the width(), innerHeight(), innerWidth(), outerHeight(), and outerWidth() methods.
  • Size: 57K, 20K gzipped

Document Ready

define([ "jquery/core/init", "jquery/core/ready" ], function( $ ) {
    $(function() {
        console.log( "ready!" );
    })
});
  • Size: 29K, 11K gzipped

Effects

define([ "jquery/effects" ], function( $ ) {
    $( "div" ).hide();
});
  • This module also gives you the fadeTo(), animate(), stop(), finish(), slideDown(), slideUp(), slideToggle(), fadeIn(), fadeOut(), and fadeToggle() methods, as well as the animation-aware versions of toggle(), show(), and hide().
  • Size: 66K, 22K gzipped

Events

define([ "jquery/event" ], function( $ ) {
    $( "div" ).on( "click", function() {
        console.log( "click!" );
    });
});
  • This module also gives you the one(), off(), trigger(), and triggerHandler() methods, as well as the jQuery special events system.
  • Size: 37K, 13K gzipped

Filtering

define([ "jquery/core/init", "jquery/traversing/findFilter" ], function( $ ) {
    console.log( $( "div" ).find( "span" ) );
});
  • This module also gives you the filter(), not(), and is() methods.
  • Size: 26K, 9.4K gzipped

Manipulation

define([ "jquery/manipulation" ], function( $ ) {
    $( "div" ).append( "Hello world" );
});
  • This module also gives you the clone(), text(), append(), prepend(), before(), after(), remove(), empty(), html(), replaceWith(), detach(), appendTo(), prependTo(), insertBefore(), insertAfter(), and replaceAll() methods.
  • Size: 46K, 16K gzipped

Offsets

define([ "jquery/offset" ], function( $ ) {
    $( "body" ).scrollTop( 1000 );
});
  • This module also gives you the offset(), position(), offsetParent(), and scrollLeft() methods.
  • Size: 59K, 20K gzipped

Parsing HTML

define([ "jquery/core", "jquery/core/parseHTML" ], function( $ ) {
    $( "<marquee>jQuery!</marquee>" ).appendTo( "body" );
});
  • Size: 46K, 16K gzipped

Properties

define([ "jquery/core/init", "jquery/attributes/prop" ], function( $ ) {
    $( "input[type=checkbox]" ).prop( "checked", true );
});
  • This module also gives you the removeProp() method.
  • Size: 28K, 10K gzipped

Traversing

define([ "jquery/traversing" ], function( $ ) {
    $( "img" ).closest( "div" );
});
  • This module also gives you the has(), closest(), index(), add(), addBack(), parent(), parents(), parentsUntil(), next(), prev(), nextAll(), prevAll(), nextUntil(), prevUntil(), siblings(), children(), and contents() methods.
  • Size: 28K, 10K gzipped

Values

define([ "jquery/core/init", "jquery/attributes/val" ], function( $ ) {
    $( "input" ).val( "hello world" );
});
  • Size: 28K, 10K gzipped

Wrapping

define([ "jquery/manipulation", "jquery/wrap" ], function( $ ) {
    $( "div" ).wrap( document.createElement( "div" ) );
});

So can I use this?

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.

A New jQuery Plugin Pattern

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.

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.

Limitations

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?

Comments

  • Pingback: jQuery: Using Only What You Need - Telerik Deve...()

  • FYI, typo/bug in the byline. It says, ” June 5, 2014. The literal quote is there. Feel fee to delete this once fixed.

    • Hmm. Now it has a circle with an X in it. Not sure what that is supposed to mean. I’m assuming i can’t delete the article. 😉

      • devreltelerik

        It was a missing icon font (and then somehow the character code changed)

  • It would be great to know the browser dependencies for each of the jQuery modules. For example can I use the `core` module in a Node.js context?

    • Off the top of my head I believe that “deferred” is the only module safe for usage outside of the browser. Even the “core” module has plenty of references to window and document. I know a lot of people use jQuery in Node with jsdom: https://github.com/tmpvar/jsdom.

  • Stephen Tindall

    Would serving jQuery modules individually limit or eliminate the use of a CDN?

  • Pingback: jQuery: Using Only What You Need - Telerik Deve...()

  • Peter Baylies

    You might consider mentioning before you use the acronym what AMD is and stands for, apart from being a software company.

  • Darren Nelsen

    The dependency line for sizzle is incorrect in your example (index.html). It should be:
    “sizzle”: “bower_components/jquery/src/sizzle/dist/sizzle”

  • Bernhard Schelling

    what about jquery mobile? ist there some similar stuff?

    • Yes, jQuery Mobile also uses AMD to separate its modules internally.

      • Bernhard Schelling

        does that mean I don’t have to do that manually, its already built in? thx

        • It’s built in. You can use a similar approach that is shown in this article. bower install jquery-mobile and then add your jQuery Mobile dependencies in your define() calls.

  • Original compressed jQuery is about 32kb. I feel AMD utilization is a first step towards better performance. And future-proofing you’re website along side Angularjs.

    • Do we really have to justify everything for “Angular”?

      • nope. but i wanted to type abt my favorite 😀

  • Nice to see jQuery doing some developing but i don’t really like to split everything up. I find class-selectors to be a basic feature but this says i need to include it separately? This (and other includes) makes it a bit hard to see what you do and do not need. My fellow developers might not realise what dependancies they need and remove stuff we need or add stuff that in the end isn’t needed.

    Also i’d like to point out that there is often more to gain from losing 1 or more pictures, dropping some html (or lazy load it) or using modernizer or RequireJS to decide whether you need a certain file. Cropping jQuery isn’t needed yet in my point of view, unless you really have no other option.

    What i do like is that it will be easier to implement modules that are now plugins and even replace core modules by better or more functional ones (if your project requires it). I just feel that the documentation needs to be really solid or we’ll be seeing lots of sites staying with jQ2.0

    • I find that it’s easy to find when you’re missing a dependency (as you get an easy to debug error), but it’s hard to remove dependencies that you’re not using. And if you don’t trim those dependencies you defeat the whole purpose of using the modules in the first place.

      jQuery is definitely a bit tricky in this regard because it wasn’t written with AMD usage in mind—that is, all methods are appended to the global $ variable—so it’s really, really hard to determine which modules are being used. If jQuery were being written from scratch it would work differently, but the structure of jQuery is so widespread that it cannot change at this point. The best option we have is to use these AMD modules.

      I completely agree that this is an ultra optimization, and that the vast majority of sites have something way more important they should do first. (I even wrote an article about it: http://flippinawesome.org/2014/03/10/is-jquery-too-big-for-mobile/.) That being said some sites do need this sort of fine-grained control. We get a lot of requests for this.

  • Andrew Slane

    I agree this would improve the first view on a browser with an empty cache, but isn’t it far more likely that a site’s visitor already has jQuery cached (especially if you’re loading it from a CDN such as Google)? Assuming that, this would actually slow down the first view because of the extra request(s) to load the piece(s) of jQuery you need. For me, it makes more sense to keep loading the full library (from a CDN) asynchronously at the end of the file whilst using vanilla-JS inline for anything needed initially at render-time. It’s easy, it works, and it doesn’t require me to rethink dependencies when updating my code.

    Still a very cool idea. Thanks for the write-up! If it catches on as the standard way of including jQuery, it could definitely offer improvements down the road.

    • If you’re building a single-page app that uses AMD and jQuery extensively, then it makes sense to bundle jQuery into the file built by rjs. I can see benefits either way. I prefer to actually list jQuery as a dependency in the modules that depend on it, so it practically has to be bundled.

    • The chances of getting a cache git from third-party CDNs such as Google are shockingly low. (See http://www.stevesouders.com/blog/2013/03/18/http-archive-jquery/ for details.)

      The idea here, as @JoeZimJS:disqus mentions, is that you would bundle jQuery into your built file with r.js. If you use the individual pieces of jQuery you reduce the size of your final bundle.

      That being said this is a bit of an ultra optimization. For the vast majority of sites loading the full jQuery library right before </body> is just fine—especially if you’re using the majority of jQuery anyways. I think the optimization is more appropriate for plugins, where it’s always a good idea to keep your dependencies slim.

      • Interesting read—I hadn’t seen that article. I definitely agree with you on the plugins versus sites argument. It makes the most sense to build it in with as little extra weight as possible. I rarely, if ever, develop plugins though, so clearly my thinking wasn’t in that realm. And I guess with sites then, the biggest benefit of using a CDN would be if you’re serving your site from a single server—it could allow a quicker lookup and download if it’s cached across servers globally via a CDN.

  • Santosh K. Shah

    How do I fill up app.js I read your post from top to bottom. After creating app.js I have no idea how to move forward. Do I need to compile. Can you please explain how can I generate code in app.js

  • Grate. I think this method is fine in cases witch you don’t have modules that load conditionally, because in that cases finally all the required modules must be loaded, so excluding the parts of jQuery you don’t need and make a custom build of jQuery must be more permanent than using individual modules. Because in the first approach, browser makes a single http request instead of multiple requests for separated modules.

  • Guest

    Grate. I think this method is fine in cases witch you don’t have modules that load conditionally, because in that cases all the required modules will be load finally, so excluding the parts of jQuery you don’t need and make a custom build of jQuery must be more permanent than using individual modules. Because in the first approach, browser makes a single http request instead of multiple requests for separated modules.

  • Grate. I think this method is fine in cases witch you don’t have modules that load conditionally, because in that cases all the required modules will be load finally, so excluding the parts of jQuery you don’t need and make a custom build of jQuery must be more permanent than using individual modules. Because in the first approach, browser makes a single http request instead of multiple requests for separated modules.

  • Pingback: Trimming Down jQuery With Grunt | Telerik Developer NetworkTelerik Developer Network()

  • Pingback: Is it possible to import only Ajax method from jQuery with Webpack? - codeengine()

  • Is there anyway to ignore the sizzle working this way?