Modularisation system is a part of every modern language that is used for creating complex applications. JavaScript is special here - modularisation system is not a part of language itself. We got great standards (CommonJS or AMD) and tools to make modularisation work. One of the most popular is Webpack - a module bundler which comes with variety of so-called loaders, transforming the input code.

Unfortunately modularisation systems not within the language creates compatibility problems. There are libraries that can’t understand modules and assumes that dependencies are available globally under predefined names.

I had this issue today. I wanted to use the great Chosen library for multi-select inputs. Unfortunately Chosen expects to have jQuery defined in the global namespace and is unaware of modules at all. In this article I’d like to show you how to integrate such module-unaware libraries with Webpack without introducing global variables.

Goal & Initial State

My goal was to allow Chosen to extend my jQuery instance without exposing the jQuery instance as a global. I have jQuery in my project installed using npm install --save jquery command - as a module. So every time I need to use jQuery I need to import it manually (here, using ES2015 syntax):

import $ from 'jquery';

Chosen can come as a bower package or as a standalone JS file with some CSS files and images attached. I decided to take the latter approach and put it into a vendor/ folder in my project. So I put the chosen.jquery.js file into the vendor folder.

As have been said before, chosen.jquery.js does not understand modules - it needs to have jQuery defined under jQuery name in global namespace. With many libraries like that it is also desirable to have this pointing to the window variable which is not granted in many environments. For example Babel.js assumes that every input it takes is a ES2015 module and this is pointing to null in such case (more about it here). So if you integrate babel-loader to use ES2015 syntax you may experience problems with such libraries.

Having this problem defined, let’s take a look how the perfect solution can be achieved - so have jQuery still as a module without exposing it as a global but being able to use libraries that assumes jQuery is defined as a global.

Solution - imports-loader

Webpack has a special loader for handling cases like that. It is called imports-loader and you need to install it by yourself - it doesn’t come by default when you install webpack. Fortunately the installation process is straightforward:

npm install --save-dev imports-loader

Then you need to modify your webpack config. In loaders section of your config you need to add the following code:

{ test: /vendor\/.+\.(jsx|js)$/,
  loader: 'imports?jQuery=jquery,$=jquery,this=>window'
}

Let’s get through this loader definition. The first part, imports defines that we want to use imports-loader. After ? there is a list of values, separated by a comma. First one jQuery=jquery defines that under the jQuery variable in loaded code there will be a result of require("jquery") expression - in our case, our jQuery library from the module. The second one is the same, but the name is different. If you want to name your variable the same as your module, you can skip the part after the equation sign and the equation sign itself. So jquery would create a jquery variable with require("jquery") value in the loaded code.

The last value is about redefining the global variable. So global this will point to window in the loaded code. It is an equivalent of wrapping the whole contents of the file with the function(this) { ... } and calling this function in-place with window as an argument. You can do the same with any global variables and it is indicated by an arrow (=>).

That’s it! Now I can do:

import $ from 'jquery';
import "vendor/chosen.jquery";

And I can use chosen jQuery extension without any problems!

Summary

As you can see, integrating module-unaware libraries without exposing its dependencies as globals is a rather simple task. In fact I thought it’ll be harder - I’ve seen many webpack configurations where popular libraries/frameworks like jQuery or Angular.js were exposed as global variables. I hope that you’ll be able to integrate your favourite libraries that way just like I did with Chosen.

comments powered by Disqus