Webpacking a project

This week I used Webpack at work and it was great. Let me tell you about it.

I took over an experimental JavaScript project with the aim of making it a proper part of our software engineering process. This means cleaning up the code, integrating it with our build system, ensuring that it has essential documentation (“how to run it”), etc.

The code didn’t look too bad. It’s a browser-based tool with a single HTML file and a couple of thousands lines of JavaScript split across a dozen files. There was no build step - all the JavaScript files were included in HTML with <script> tags, and the dependencies were checked into the repo. There were some bad signs, too: there were no tests and the code was weirdly formatted. Still, it was clean enough. And at least there was no sign of Angular!

As the first step, I ran all the code through js-beautify. Did you know that Spacemacs has a shortcut for running js-beautify? It’s SPC m =. This took care of all the weird indentation.

The code was split into one object/“class” per file. It was almost modular style, except that of course all the files share the same scope. There was a main module that uses all the other modules, but also defines some global variables that the other modules use.

I figured out that it’s a good idea to set up JSHint. The initial run didn’t reveal any problems beyond some missing semicolons, but I didn’t want to break anything. Since JSHint does not know that the files share the same scope, I had to add all the global variables to the globals array in .jshintrc.

The global variables containing the application state made me feel uneasy. Since the code was almost modular already, I realized it would be easy to use Webpack to get real modules. Webpack (like browserify) takes your code, wraps each file into a module and gives you Node-style interface with require() and module.exports. Using modules would make the global state explicit and Webpack would also make it easy to use ES6, to have live-reloading code, and other good stuff.

What I did was to add an entrypoint that requires the main module and calls the function that starts the application. Initially I only included the application code in webpack and pulled in the dependencies in the old <script> way.

Then I removed the module names from JShint globals one-by-one. JShint gave me a list of all the files that needed attention and I added the relevant imports and exports there.

To deal with the global state variables, I added a new module called GlobalState to contain them. If I have time to refactor this later, finding all the places that use it is just one grep away.

Most of the third-party dependencies could be pulled from NPM. Create a package.json with your dependencies, sadd node_modules to Webpack’s resolve roots, run npm install and you’re good to go.

With the more obscure libraries, I decided to keep the vendored libraries in the repository and use shimming to make them work with Webpack. For example to use roslibjs, I added the following loader:

// roslibjs requires EventEmitter2 and defines ROSLIB
{
  test: /roslib\.js$/,
  loaders: [
    'imports?EventEmitter2=eventemitter2',
    'exports?ROSLIB'
  ]
}

Then I could do var ROSLIB = require('roslib.js') anywhere I wanted.

When integrating Webpack with rest of our build system, I wanted to pass it the resolve roots as a command-line flag. Webpack does not have such a flag out-of-the-box. This is not a problem since webpack.config.js is an ordinary Node module and you can use e.g. yargs to parse the flags:

var argv = require(’yargs’).argv;
module.exports = {
  resolve: {
    root: argv.resolveRoot 
  }
};
// now do webpack —resolve-root=foo —resolve-root=bar

I like the declarative configuration of Webpack. Basically you say “here is my code, here are my deps, please compile” and it does what you want. To get ES6, just add babel-loader to the loaders. To get live-reloading, just run the dev server with --hot --inline. This is so much better than complex gulpfiles. If only the documentation was less confusing!


Comments or questions? Tweet to me or send me an e-mail.