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!