How to JavaScript (or yet another JavaScript guide) Part 9 — Generating a library with Webpack

Up until now we’ve seen how to setup our project so that it has the tools necessary to help our work, but what about the project itself? We have yet to create something out of it because let’s face it, as of now we aren’t doing much our of it. There are different paths that we can go from here, and today we’ll be seeing how to generate a JavaScript library that can be distributed from (for example) a CDN, so that it can be included via HTML’s script tag and be used by anyone. So let’s start.


Our library will display an alert with the message ‘Hello universe!’ when it is loaded. This is not very useful, since the user has no control on the message that is displayed and neither when the message is displayed. This would change if we could make our class Popup available as a global, like what happens when we load libraries like jquery or lodash in the browser, so that the user can use it as it sees fit. How can we accomplish this, if the code in our bundle is contained inside its own scope? Fortunately webpack helps us accomplish this, as described in https://webpack.github.io/docs/library-and-externals.html#examples, so let’s try it.

First we change our configuration as follows, by adding a new entry library:

entry: resolve(_dirname, 'src/index.js'),
output: {
path: path.resolve(_dirname, 'dist'),
filename: 'bundle.js',
library: 'Popup'
},

What this means is that we want to make available the exported value from the entry module (src/index.js) as a global variable called Popup. For convenience we’ll also need to do a small adjustment to how we export the value, open src/index.js and substitute its content with the following line:

module.exports = require('./popup.js').default;
You may ask why we’re going back to using module.export instead of export, this is so that the exported library object is the Popup class itself, which is something I couldn’t accomplish with the export keyword. More at https://gist.github.com/junhanamaki/958e74889f7ad7da1f069927c29fa439.

Now fire up webpack-dev-server and go to http://localhost:8080/bundle, open the console and you should have access to class Popup:

Using class Popup from console
Loading http://localhost:8080/webpack-dev-server/bundle won’t allow us to easily access Popup, because it loads our code inside an iframe. Also note that code changes won’t be loaded automatically when accessing through http://localhost:8080/bundle

Looks good, so let’s bundle our code. Just run the following command and the bundle will be available as dist/bundle.js:

$ npm run build

And we’re done… But wait, look at the size of our bundle.js, it’s over 1Mb! But our code is less than 20 lines! What gives?! Well as you may have guessed, this happens because we’re bundling lodash, reason why the file got so big. Now, there’s nothing wrong in doing so, but we could choose not to bundle lodash and assume that it will be available as a global, thus decreasing the size of our bundle, and for the purpose of this post we’ll assume just that.

lodash also makes available its functions as packages, so we could just include the one corresponding to capitalize to decrease the size of our bundle significantly.

Fortunately it’s quite simple to achieve this when using webpack. Following the instructions here, we’ll add the following line to our webpack.config.js:

  // ...
externals: {
lodash: 'lodash'
}
};

This will indicate webpack to use lodash defined as global variable, so it will exclude it from our bundle. Running build again and we now should have a significantly smaller file than before, nice! Unfortunately we’re still not done yet, because these changes has the side effect of breaking our code when we run the server, since now it won’t recognize lodash… We can fix this by using different configurations based on environment, by improving on what we already have in webpack.config.js. There are several ways to achieve this, and the following is the solution that I arrived:

webpack.config.js with environment based configuration

So we created three configurations based on the value set in NODE_ENV, corresponding to:

  • dev_bundle: configuration to generate bundle for development, thus including source map (notice how we changed from inline to regular source map, so that is generated in another file);
  • prod_bundle: configuration to generate bundle for production, where the code is minified;
  • development (default): our original configuration, which is used during our development process.

And we also need to update our package.json to make use of this new configurations. Just change the value under scripts as follows:

"scripts": {
"start": "node_modules/.bin/webpack-dev-server --progress",
"rebuild": "npm run clear & npm run build",
"build": "npm run build:dev & npm run build:prod",
"build:dev": "NODE_ENV=dev_bundle node_modules/.bin/webpack",
"build:prod": "NODE_ENV=prod_bundle node_modules/.bin/webpack",
"clear": "rm -rf dist",
"test": "node_modules/.bin/mocha --compilers js:babel-register",
"test:browser": "node_modules/.bin/karma start"
},

We now have three new scripts, one to build our bundle with dev_bundle’s configuration, another for prod_bundle, and a rebuild, which will clear the folder before doing build. We also updated our build script to build both the dev_bundle and prod_bundle at the same time. We keep the rest the same, so that it uses the original configuration.

All we have to do is run the following command to generate the bundles:

$ npm run rebuild

Under the dist folder it should be the bundled file and also a corresponding minimized version. And we’re finally done, we’ve managed to generate a JavaScript library from a modular project using webpack!

Today’s code is available here.

Thanks for reading!