webpack and jQuery: Include only the parts you need

Recently I decided to try out webpack as a replacement for Browserify, and it didn’t disappoint! Out-of-the-box, webpack even support’s AMD, so RequireJS-based libraries can be used as-is. This got me thinking, jQuery switched to RequireJS a while back for their source code and build process, so I questioned:

Could webpack be used to load individual jQuery modules?

As web technologies advance, it is getting harder to justify loading large frameworks, even one a powerful and flexible as jQuery. A whike back, jQuery made it possible to build custom builds of jQuery, but this solution is not very flexible and requires extra maintenance. It would be great if there was a way to only package modules you are using, and dynamically discard the bloat of unused functionality.

With webpack, I’m happy to say this is possible!

However, there are a few hoops to jump through, so I’ve assembled this tutorial.

This tutorial assumes basic knowledge of Node, NPM, and webpack and how to use them. If there tools are unfamiliar to you, you should start there.

 

Step 1: Initialize Your Project

First we need a project to work with. For sake of example I will initialize a Node.JS project in a folder jquery-tutorial using the default settings.

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (jquery-tutorial)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to .../jquery-tutorial/package.json:

{
  "name": "jquery-tutorial",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes)

 

Step 2: Install Dependencies

Now we need to install our dependencies, such as jQuery.

Dependency 1: jquery

This is easy, just install jQuery through NPM. In this tutorial, I will use the default latest version, but a different version can be specified.

$ npm install --save-dev jquery
npm WARN package.json jquery-tutorial@1.0.0 No description
npm WARN package.json jquery-tutorial@1.0.0 No repository field.
npm WARN package.json jquery-tutorial@1.0.0 No README data
jquery@2.1.3 node_modules/jquery

Dependency 2: sizzle

The jQuery package will not install this itself, and since the vast-majority of jQuery depends on jQuery’s Sizzle selector engine, you will probably want to install this. Again, I am using the latest version.

$ npm install --save-dev sizzle
npm WARN package.json jquery-tutorial@1.0.0 No description
npm WARN package.json jquery-tutorial@1.0.0 No repository field.
npm WARN package.json jquery-tutorial@1.0.0 No README data
sizzle@2.1.1 node_modules/sizzle

Dependency 3: amd-define-factory-patcher-loader

At this point, you are probably wondering what the heck that is, and why you need it. In a nutshell, one of jQuery’s source files is not compatible with spec-compliant AMD loaders. In RequireJS’s define implementation, the factory argument is optional, a quirk one of jQuery’s modules has come to depend on. I have an open pull request for this issue on the jQuery GitHub repository, however it has not yet been merged (update: my code has been merged, but it will not be available in the core until jQuery 3.0.0). As a solution for these instances, I created this webpack loader, so these problem files can be fixed on-the-fly. More on how to use this loader below.

$ npm install --save-dev amd-define-factory-patcher-loader
npm WARN package.json jquery-tutorial@1.0.0 No description
npm WARN package.json jquery-tutorial@1.0.0 No repository field.
npm WARN package.json jquery-tutorial@1.0.0 No README data
amd-define-factory-patcher-loader@1.0.0 node_modules/amd-define-factory-patcher-loader
├── estraverse@3.1.0
├── esprima@2.1.0
└── escodegen@1.6.1 (esutils@1.1.6, estraverse@1.9.3, esprima@1.2.5, source-map@0.1.43, optionator@0.5.0)

 

Step 3: webpack config file

In order to use the loader, we are going to need to specify which files need transforming in our config file. Here is the file we will use in this tutorial, which will take care of the problematic jquery/src/selector.js file by passing it through the loader.

webpack.config.js

module.exports = {
    entry: './entry',
    output: {
        filename: 'bundle.js'
    },
    module: {
        loaders: [
            { test: /jquery[\\\/]src[\\\/]selector\.js$/, loader: 'amd-define-factory-patcher-loader' }
        ]
    }
};

(The regex is ugly, because we apparently have to accommodate Windows-style path separators.)

 

Step 4: Application Files

To test our application, we are going to need an HTML file to load it into, and out main JS file. Create the following two file.

test.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>jQuery webpack test</title>
    </head>
    <body>
        <script type="text/javascript" src="bundle.js"></script>
    </body>
</html>

entry.js

//main application entry point

 

Step 5: Using jQuery

In order to load the jQuery core, all we need is the following code.

var $ = require('jquery/src/core');

However, by itself the jQuery core does not do much. You are going to need to include some of the other modules. All the modules use the same jQuery object, so it is unnecessary to set the to a variable. See the following examples:

Example 1: append

This example will append a new element to the body element.

entry.js

var $ = require('jquery/src/core');
require('jquery/src/core/init');
require('jquery/src/manipulation');

$('body').append('<p>Success!</p>');

Compiled with the webpack command, using the --display-modules argument, we can see the modules that get bundled.

$ webpack --display-modules
Hash: 76a6e723d2aec85adf2d
Version: webpack 1.7.3
Time: 276ms
    Asset    Size  Chunks             Chunk Names
bundle.js  145 kB       0  [emitted]  main
   [0] ./entry.js 143 bytes {0} [built]
   [1] ./~/jquery/src/core.js 11.6 kB {0} [built]
   [2] ./~/jquery/src/core/init.js 3.4 kB {0} [built]
   [3] ./~/jquery/src/manipulation.js 15 kB {0} [built]
   [4] ./~/jquery/src/var/arr.js 36 bytes {0} [built]
   [5] ./~/jquery/src/var/slice.js 62 bytes {0} [built]
   [6] ./~/jquery/src/var/concat.js 63 bytes {0} [built]
   [7] ./~/jquery/src/var/push.js 61 bytes {0} [built]
   [8] ./~/jquery/src/var/indexOf.js 64 bytes {0} [built]
   [9] ./~/jquery/src/var/class2type.js 64 bytes {0} [built]
  [10] ./~/jquery/src/var/toString.js 86 bytes {0} [built]
  [11] ./~/jquery/src/var/hasOwn.js 92 bytes {0} [built]
  [12] ./~/jquery/src/var/support.js 99 bytes {0} [built]
  [13] ./~/jquery/src/core/var/rsingleTag.js 91 bytes {0} [built]
  [14] ./~/jquery/src/traversing/findFilter.js 2.46 kB {0} [built]
  [15] ./~/jquery/src/core/access.js 1.21 kB {0} [built]
  [16] ./~/jquery/src/manipulation/var/rcheckableType.js 59 bytes {0} [built]
  [17] ./~/jquery/src/manipulation/support.js 975 bytes {0} [built]
  [18] ./~/jquery/src/data/var/data_priv.js 66 bytes {0} [built]
  [19] ./~/jquery/src/data/var/data_user.js 66 bytes {0} [built]
  [20] ./~/jquery/src/data/accepts.js 383 bytes {0} [built]
  [21] ./~/jquery/src/traversing.js 4.54 kB {0} [built]
  [22] ./~/jquery/src/event.js 24.5 kB {0} [built]
  [23] ./~/jquery/src/selector.js 47 bytes {0} [built]
  [24] ./~/jquery/src/traversing/var/rneedsContext.js 110 bytes {0} [built]
  [25] ./~/jquery/src/data/Data.js 4.88 kB {0} [built]
  [26] ./~/jquery/src/var/strundefined.js 50 bytes {0} [built]
  [27] ./~/jquery/src/var/rnotwhite.js 42 bytes {0} [built]
  [28] ./~/jquery/src/event/support.js 123 bytes {0} [built]
  [29] ./~/jquery/src/selector-sizzle.js 294 bytes {0} [built]
  [30] ./~/sizzle/dist/sizzle.js 59.1 kB {0} [built]

Example 2: css

This example will select the body element at set the background to red.

entry.js

var $ = require('jquery/src/core');
require('jquery/src/core/init');
require('jquery/src/css');

$('body').css('background', 'red');

Compilation output.

$ webpack --display-modules
Hash: 92715ad3278fbf95fefa
Version: webpack 1.7.3
Time: 314ms
    Asset    Size  Chunks             Chunk Names
bundle.js  185 kB       0  [emitted]  main
   [0] ./entry.js 133 bytes {0} [built]
   [1] ./~/jquery/src/core.js 11.6 kB {0} [built]
   [2] ./~/jquery/src/core/init.js 3.4 kB {0} [built]
   [3] ./~/jquery/src/css.js 12.3 kB {0} [built]
   [4] ./~/jquery/src/var/pnum.js 80 bytes {0} [built]
   [5] ./~/jquery/src/core/access.js 1.21 kB {0} [built]
   [6] ./~/jquery/src/css/var/rmargin.js 45 bytes {0} [built]
   [7] ./~/jquery/src/css/var/rnumnonpx.js 113 bytes {0} [built]
   [8] ./~/jquery/src/css/var/cssExpand.js 70 bytes {0} [built]
   [9] ./~/jquery/src/css/var/isHidden.js 355 bytes {0} [built]
  [10] ./~/jquery/src/css/var/getStyles.js 410 bytes {0} [built]
  [11] ./~/jquery/src/css/curCSS.js 1.45 kB {0} [built]
  [12] ./~/jquery/src/css/defaultDisplay.js 1.87 kB {0} [built]
  [13] ./~/jquery/src/css/addGetHookIf.js 509 bytes {0} [built]
  [14] ./~/jquery/src/css/support.js 3.2 kB {0} [built]
  [15] ./~/jquery/src/css/swap.js 555 bytes {0} [built]
  [16] ./~/jquery/src/core/ready.js 2.38 kB {0} [built]
  [17] ./~/jquery/src/var/arr.js 36 bytes {0} [built]
  [18] ./~/jquery/src/var/slice.js 62 bytes {0} [built]
  [19] ./~/jquery/src/var/concat.js 63 bytes {0} [built]
  [20] ./~/jquery/src/var/push.js 61 bytes {0} [built]
  [21] ./~/jquery/src/var/indexOf.js 64 bytes {0} [built]
  [22] ./~/jquery/src/var/class2type.js 64 bytes {0} [built]
  [23] ./~/jquery/src/var/toString.js 86 bytes {0} [built]
  [24] ./~/jquery/src/var/hasOwn.js 92 bytes {0} [built]
  [25] ./~/jquery/src/var/support.js 99 bytes {0} [built]
  [26] ./~/jquery/src/core/var/rsingleTag.js 91 bytes {0} [built]
  [27] ./~/jquery/src/traversing/findFilter.js 2.46 kB {0} [built]
  [28] ./~/jquery/src/data/var/data_priv.js 66 bytes {0} [built]
  [29] ./~/jquery/src/selector.js 47 bytes {0} [built]
  [30] ./~/jquery/src/manipulation.js 15 kB {0} [built]
  [31] ./~/jquery/src/deferred.js 4.41 kB {0} [built]
  [32] ./~/jquery/src/traversing/var/rneedsContext.js 110 bytes {0} [built]
  [33] ./~/jquery/src/data/Data.js 4.88 kB {0} [built]
  [34] ./~/jquery/src/selector-sizzle.js 294 bytes {0} [built]
  [35] ./~/jquery/src/manipulation/var/rcheckableType.js 59 bytes {0} [built]
  [36] ./~/jquery/src/manipulation/support.js 975 bytes {0} [built]
  [37] ./~/jquery/src/data/var/data_user.js 66 bytes {0} [built]
  [38] ./~/jquery/src/data/accepts.js 383 bytes {0} [built]
  [39] ./~/jquery/src/traversing.js 4.54 kB {0} [built]
  [40] ./~/jquery/src/event.js 24.5 kB {0} [built]
  [41] ./~/jquery/src/callbacks.js 5.51 kB {0} [built]
  [42] ./~/jquery/src/var/rnotwhite.js 42 bytes {0} [built]
  [43] ./~/jquery/src/var/strundefined.js 50 bytes {0} [built]
  [44] ./~/jquery/src/event/support.js 123 bytes {0} [built]
  [45] ./~/sizzle/dist/sizzle.js 59.1 kB {0} [built]

Example 3: ajax

This example will create an AJAX request using XHR to load the page it is on. This example needs to be run from a server or by running our browser with the appropriate permissions.

entry.js

var $ = require('jquery/src/core');
require('jquery/src/ajax');
require('jquery/src/ajax/xhr');

$.ajax({
    url: '?test=1',
    cache: false,
    complete: function(jqXHR, textStatus) {
        console.log(textStatus);
        console.log(jqXHR.responseText);
    }
});

Compilation output.

$ webpack --display-modules
Hash: ec02b41a0c9556f5ff45
Version: webpack 1.7.3
Time: 243ms
    Asset    Size  Chunks             Chunk Names
bundle.js  127 kB       0  [emitted]  main
   [0] ./entry.js 248 bytes {0} [built]
   [1] ./~/jquery/src/core.js 11.6 kB {0} [built]
   [2] ./~/jquery/src/ajax.js 21.2 kB {0} [built]
   [3] ./~/jquery/src/ajax/xhr.js 3.49 kB {0} [built]
   [4] ./~/jquery/src/var/arr.js 36 bytes {0} [built]
   [5] ./~/jquery/src/var/slice.js 62 bytes {0} [built]
   [6] ./~/jquery/src/var/concat.js 63 bytes {0} [built]
   [7] ./~/jquery/src/var/push.js 61 bytes {0} [built]
   [8] ./~/jquery/src/var/indexOf.js 64 bytes {0} [built]
   [9] ./~/jquery/src/var/class2type.js 64 bytes {0} [built]
  [10] ./~/jquery/src/var/toString.js 86 bytes {0} [built]
  [11] ./~/jquery/src/var/hasOwn.js 92 bytes {0} [built]
  [12] ./~/jquery/src/var/support.js 99 bytes {0} [built]
  [13] ./~/jquery/src/var/rnotwhite.js 42 bytes {0} [built]
  [14] ./~/jquery/src/ajax/var/nonce.js 73 bytes {0} [built]
  [15] ./~/jquery/src/ajax/var/rquery.js 40 bytes {0} [built]
  [16] ./~/jquery/src/core/init.js 3.4 kB {0} [built]
  [17] ./~/jquery/src/ajax/parseJSON.js 222 bytes {0} [built]
  [18] ./~/jquery/src/ajax/parseXML.js 485 bytes {0} [built]
  [19] ./~/jquery/src/deferred.js 4.41 kB {0} [built]
  [20] ./~/jquery/src/core/var/rsingleTag.js 91 bytes {0} [built]
  [21] ./~/jquery/src/traversing/findFilter.js 2.46 kB {0} [built]
  [22] ./~/jquery/src/callbacks.js 5.51 kB {0} [built]
  [23] ./~/jquery/src/traversing/var/rneedsContext.js 110 bytes {0} [built]
  [24] ./~/jquery/src/selector.js 47 bytes {0} [built]
  [25] ./~/jquery/src/selector-sizzle.js 294 bytes {0} [built]
  [26] ./~/sizzle/dist/sizzle.js 59.1 kB {0} [built]

 

Step 6: Conclusion

Depending on the feaures you want to use, you are going to have to determine which jQuery modules are necessary. Depending on the feature, jQuery may be rather silent about the failure. Looking though the jQuery source files can help with this.

 

10 thoughts on “webpack and jQuery: Include only the parts you need

  1. Art H says:

    Thanks for the article. Newbie at webpack.

    Your amd loader is not being applied to selector.js. I can add the factory to selector.js and all is well but the test attribute is not working. It’s a copy/paste from your article. Running under windows with the latest version of webpack and nodejs.

    { test: /jquery\/src\/selector\.js$/, loader: ‘amd-define-factory-patcher-loader’ }

    Any suggestions on how to debug?

    • Hey, sorry for the delay. That’s odd, I wonder if perhaps you are getting Windows-style path separators for the test. You could try using /jquery\\src\\selector\.js$/ . If that fails, can you put together an MCVE?

  2. Max says:

    How do you handle dynamic links in webpack? For example:

    where USER_STATUS is some variable which can have different values like disabled (./images/disabled.png), enabled, pending, etc.?

  3. Hi! Thanks for the article!

    Is there some way to load several jQuery modules with an ‘import’ syntax?
    I’m trying this, and it doesn’t work properly:

    import $ from “jquery/src/core”;
    import “jquery/src/ajax”;
    import “jquery/src/ajax/xhr”;

    Everything’s okay if there is only first line (jQuery core is loaded). But browser comes up with an error if there are 2-/3-line imports. I tried them with variables too (like import $ajax from “jquery/src/ajax”), but they don’t work. Here is the error:

    Uncaught TypeError: Cannot read property ‘match’ of undefined @ rneedsContext.js.

    Thanks a lot if you find time for helping!

  4. tomkyle says:

    Thanks a lot for that proper explanation! Diving in Webpack the fourth day now, I have one (newb) question: Your examples explicitly mention jQ core, jQ ajax, and jQ ajax/xhr. Should it not be possible to just load jQ ajax/xhr, since it internally requires jQ ajax and core?

    • I haven’t tested it, but I think you might need to keep the core require to get the jQuery object. Otherwise yes, it looks like you should be able to do this.

  5. Pingback: jquery: include only the parts you need | Webpack

  6. Pingback: Apply loader to specific file - HTML CODE

Leave a Reply

Your email address will not be published. Required fields are marked *

Are you human? * Time limit is exhausted. Please reload the CAPTCHA.