Telerik blogs
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?


TJ VanToll
About the Author

TJ VanToll

TJ VanToll is a frontend developer, author, and a former principal developer advocate for Progress.

Comments

Comments are disabled in preview mode.