Library Output

Kea - High level abstraction between React and Redux - Interview with Marius Andra

The example of the previous chapter can be expanded further to study webpack's library output options in detail.

The library target is controlled through the output.libraryTarget field. output.library comes into play as well and individual targets have additional fields related to them.

var#

The demonstration project of ours uses var by default through configuration:

webpack.lib.js

const commonConfig = merge([
  {
    ...
    output: {
      path: PATHS.build,
      library: 'Demo',
      libraryTarget: 'var',
    },
  },
  ...
]);

The output configuration maps library and libraryTarget to the following form:

var Demo =
/******/ (function(modules) { // webpackBootstrap
...
/******/ })
...
/******/ ([
  ...
/******/ ]);
//# sourceMappingURL=lib.js.map

This tells it generates var <output.library> = <webpack bootstrap> kind of code and also explains why importing the code from Node does not give access to any functionality.

window, global, assign, this#

Most of the available options vary the first line of the output as listed below:

  • window - window["Demo"] =
  • global - global["Demo"] =
  • assign - Demo = - If you executed this code in the right context, it would associate it to a global Demo.
  • this - this["Demo"] = - Now the code would associate to context this and work through Node.
You can try running the resulting code through Node REPL (use node at project root and use require('./dist/lib')).

CommonJS#

The CommonJS specific targets are handy when it comes to Node. There are two options: commonjs and commonjs2. These refer to different interpretations of the CommonJS specification. Let's explore the difference.

commonjs#

If you used the commonjs option, you would end up with code that expects global exports in which to associate:

exports["Demo"] =
...

If this code was imported from Node, you would get { Demo: { add: [Getter] } }.

commonjs2#

commonjs2 expects a global module.exports instead:

module.exports =
...

The library name, Demo, isn't used anywhere. As a result importing the module yields { add: [Getter] }. You lose the extra wrapping.

AMD#

If you remember RequireJS, you recognize the AMD format it uses. In case you can use the amd target, you get output:

define("Demo", [], function() { return /******/ (function(modules) { // webpackBootstrap
...

In other words, webpack has generated a named AMD module. The result doesn't work from Node as there is no support for the AMD format.

UMD#

Universal Module Definition (UMD) was developed to solve the problem of consuming the same code from different environments. Webpack implements two output variants: umd and umd2. To understand the idea better, let's see what happens when the options are used.

umd#

Basic UMD output looks complicated:

(function webpackUniversalModuleDefinition(root, factory) {
  if(typeof exports === 'object' && typeof module === 'object')
    module.exports = factory();
  else if(typeof define === 'function' && define.amd)
    define("Demo", [], factory);
  else if(typeof exports === 'object')
    exports["Demo"] = factory();
  else
    root["Demo"] = factory();
})(this, function() {

The code performs checks based on the environment and figures out what kind of export to use. The first case covers Node, the second is for AMD, the third one for Node again, while the last one includes a global environment.

The output can be modified further by setting output.umdNamedDefine: false:

(function webpackUniversalModuleDefinition(root, factory) {
  if(typeof exports === 'object' && typeof module === 'object')
    module.exports = factory();
  else if(typeof define === 'function' && define.amd)
    define([], factory);
  else if(typeof exports === 'object')
    exports["Demo"] = factory();
  else
    root["Demo"] = factory();
})(this, function() {
...

To understand umd2 option, you have to understand optional externals first.

Optional Externals#

In webpack terms, externals are dependencies that are resolved outside of webpack and are available through the environment. Optional externals are dependencies that can exist in the environment, but if they don't, they get skipped instead of failing hard.

Consider the following example where jQuery is loaded if it exists:

lib/index.js

var optionaljQuery;
try {
  optionaljQuery = require('jquery');
} catch(err) {} // eslint-disable-line no-empty

function add(a, b) {
  return a + b;
}

export {
  add,
};

To treat jQuery as an external, you should configure as follows:

{
  externals: {
    jquery: 'jQuery',
  },
},

If libraryTarget: 'umd' is used after these changes, you get output:

(function webpackUniversalModuleDefinition(root, factory) {
  if(typeof exports === 'object' && typeof module === 'object')
    module.exports = factory((
      function webpackLoadOptionalExternalModule() {
        try { return require("jQuery"); } catch(e) {}
      }())
    );
  else if(typeof define === 'function' && define.amd)
    define(["jQuery"], factory);
  else if(typeof exports === 'object')
    exports["Demo"] = factory((
      function webpackLoadOptionalExternalModule() {
        try { return require("jQuery"); } catch(e) {}
      }())
    );
  else
    root["Demo"] = factory(root["jQuery"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_0__) {
return /******/ (function(modules) { // webpackBootstrap
...

Webpack wrapped the optional externals in try/catch blocks.

umd2#

To understand what the umd2 option does, consider the following output:

/*! fd0ace9 */
(function webpackUniversalModuleDefinition(root, factory) {
  if(typeof exports === 'object' && typeof module === 'object')
    module.exports = factory((
      function webpackLoadOptionalExternalModule() {
        try { return require("jQuery"); } catch(e) {}
      }())
    );
  else if(typeof define === 'function' && define.amd)
    define([], function webpackLoadOptionalExternalModuleAmd() {
      return factory(root["jQuery"]);
    });
  else if(typeof exports === 'object')
    exports["Demo"] = factory((
      function webpackLoadOptionalExternalModule() {
        try { return require("jQuery"); } catch(e) {}
      }())
    );
  else
    root["Demo"] = factory(root["jQuery"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_0__) {
return /******/ (function(modules) { // webpackBootstrap

You can see one important difference: the AMD block contains more code than earlier. The output follows non-standard Knockout.js convention as discussed in the related pull request.

In most of the cases using output.libraryTarget: 'umd' is enough as optional dependencies and AMD tend to be a rare configuration especially if you use modern technologies.

JSONP#

There's one more output option: jsonp. It generates output as below:

Demo(/******/ (function(modules) { // webpackBootstrap
...

In short, output.library maps to the JSONP function name. The idea is that you could load a file across domains and have it call the named function. Specific APIs implement the pattern although there is no official standard for it.

SystemJS#

SystemJS is an emerging standard. webpack-system-register plugin allows you to wrap your output in a System.register call making it compatible with the scheme. If you want to support SystemJS this way, set up another build target.

Conclusion#

Webpack supports a large variety of library output formats. umd is the most valuable for a package author. The rest are more specialized and require specific use cases to be valuable.

To recap:

  • Most often umd is all you need. The other library targets exist more specialized usage in mind.
  • The CommonJS variants are handy if you target only Node or consume the output through bundlers alone. UMD implements support for CommonJS, AMD, and globals.
  • It's possible to target SystemJS through a plugin. Webpack does not support it out of the box.

You'll learn to manage multi-page setups in the next chapter.

Previous chapterBundling Libraries
Next chapterMultiple Pages

This book is available through Leanpub (digital), Amazon (paperback), and Kindle (digital). By purchasing the book you support the development of further content. A part of profit (~30%) goes to Tobias Koppers, the author of webpack.

Need help?