I’ve been migrating a big project that used requirejs optimizer to bundle all the javascript modules to Webpack. As everybody knows Webpack’s documentation is not the best but the community has helped writing about their experiences and doing tutorials about it.

To do my part I would like to write a series sharing my experiences during my migration.

I’ll start with Webpack’s configuration, trying to explain some properties and give more info of what’s not in the official docs, with some examples.

module.noParse

This property can be either a RegEx or an array of RegExs. All matching files will not be parsed by webpack.

module.exports = {
	//...	
	module: {
		noParse: [ /^dontParseThis$/ ]
	}
	//...
}

This means that webpack, when requiring them, will bundle them as is, without doing anything to them. This means that:

  1. These files can only have require calls to dependencies marked as external or any dependency that isn’t in any webpack chunk. Because that dependency will be “encapsulated” by webpack inside the bundle and won’t be accesible through regular require calls.
  2. You can skip big files that are already optimized, increasing the speed of the build.
  3. Although they are not parsed by webpack, they will be part of your bundle.

output.libraryTarget

You developed your magicLibrary in a very modular fashion using all the power of webpack. But have you thought how your users are going to consume it? Here’s where libraryTarget config comes in.

First a quick note, to give your library a name (you should), set the output.library config to it.

For the distribution type you have several options:

var (default)

When your library is loaded, the return value of your entry point will be assigned to a variable:

var magicLibrary = _entry_return_;
// your users will use your library like:
magicLibrary.doTheTrick();

(Not specifying a output.library will cancel this var configuration)

this

When your library is loaded, the return value of your entry point will be assigned to this, the meaning of this is up to you:

this["magicLibrary"] = _entry_return_;
// your users will use your library like:
this.magicLibrary.doTheTrick();
magicLibrary.doTheTrick(); //if this is window

this will depend mostly on how you are injecting the bundle.

commonjs

When your library is loaded, the return value of your entry point will be part of the exports object. As the name implies, this is used in commonjs environments.

exports["magicLibrary"] = _entry_return_;
//your users will use your library like:
require("magicLibrary").doTheTrick();

commonjs2

When your library is loaded, the return value of your entry point will be part of the exports object. As the name implies, this is used in commonjs environments.

module.exports = _entry_return_;
//your users will use your library like:
require("magicLibrary").doTheTrick();

Wondering the difference between commonjs and commonjs2? Check this out. (They are pretty much the same)

amd (Asynchronous Module Definition)

In this case webpack will surround you library with an AMD.

But there is a very important pre-requisite, your entry chunk must be defined with the define property, if not, webpack wil create the AMD module, but without dependencies. I learned this the hard way, it’s logical but not obvious I think. Anyway… the output will be something like this:

define([], function() {
	//what this module returns is what your entry chunk returns
});

But if you download this script, first you may get a error: define is not defined, it’s ok! if you are distributing your library as with amd, then your users need to use require to load it. But, require([_what_])?

output.library!

output: {
	name: "magicLibrary",
	libraryTarget: "amd"
}

And the module will be:

define("magicLibrary", [], function() {
	//what this module returns is what your entry chunk returns
});

// And then your users will be able to do:
require(["magicLibrary"], function(magic){
	magic.doTheTrick();
});

umd (Universal Module Definition)

This is a way for your library to work with all module definitions (and where aren’t modules at all). It will work with commonjs, amd and as global variable.

Here to name your module you need the another property:

output: {
	name: "magicLibrary",
	libraryTarget: "umd",
	umdNamedDefine: true
}

And finally the output is:

(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("magicLibrary", [], factory);
	else if(typeof exports === 'object')
		exports["magicLibrary"] = factory();
	else
		root["magicLibrary"] = factory();
})(this, function() {
	//what this module returns is what your entry chunk returns
});

Module proof library.

externals

Externals can be tricky, specially with all the ways you have for defining them and the combination with the output.libraryTarget configuration. These dependencies won’t be resolved by webpack, but should become dependencies of the resulting bundle. The kind of the dependency depends on output.libraryTarget.

I’ll paste the official documentation that I think is complete and add comments.

externals: [
    {
        a: false, // a is not external
        b: true, // b is external (require("b"))
        "./c": "c", // "./c" is external (require("c"))
        "./d": "var d" // "./d" is external (d)
    },
    // Every non-relative module is external
    // abc -> require("abc")
    /^[a-z\-0-9]+$/,
    function(context, request, callback) {
        // Every module prefixed with "global-" becomes external
        // "global-abc" -> abc
        if(/^global-/.test(request))
            return callback(null, "var " + request.substr(7));
        callback();
    },
    "./e" // "./e" is external (require("./e"))
]
type value resulting import code
“var” "abc" module.exports = abc;
“var” "abc.def" module.exports = abc.def;
“this” "abc" (function() { module.exports = this["abc"]; }());
“this” ["abc", "def"] (function() { module.exports = this["abc"]["def"]; }());
“commonjs” "abc" module.exports = require("abc");
“commonjs” ["abc", "def"] module.exports = require("abc").def;
“amd” "abc" define(["abc"], function(X) { module.exports = X; })
“umd” "abc" everything above

All the ways to set an external dep are easy to understand with the official doc.

The only thing I’d recommend is to not mix your libraryTarget and the type of the externals. Like:

output:{
	libraryTarget: "amd"
},
externals = [
	{
		"./extLibrary": "var extLibrary"
	}
}

This would be invalid, as extLibrary wouldn’t exist as a variable in the final bundle, as the bundle is defined as amd.

Webpack will give more importance to your libraryTarget than to your external type, “correcting” them. But I wouldn’t relay on it that much. Keep it consistent!

Some official warnings before ending with this configuartion:

Enforcing amd or umd in a external value will break if not compiling as amd/umd target.

Note: If using umd you can specify an object as external value with property commonjs, commonjs2, amd and root to set different values for each import kind.

Hope it helped, give me feedback if I’m missing something or if I’m wrong in something!

Blog Logo

Tomas Alabes

Software Engineer, author, blogger and obsessive learner, from Argentina living in Silicon Valley


Published

Image

Tomas Alabes' Blog

My personal site's blog

Back to Overview