Write Better Frontend Modules with Webpack

Harness the power of modules for your frontend development—and let Adam be your guide.

Webpack is a relatively new addition to the swirling chaos that is the frontend development build tool maelstrom. Before you run away screaming about pushing the web forward, let’s walk through what Webpack is and how it can alleviate some major frontend developer pain.

What are Modules?

Modules are small, typically self-contained chunks of code. Frontend developers love modules (or components or pods or snippets or partials). Webpack lets you write JavaScript in modules (CommonJS modules to be specific), and it bundles them up into a single file (or files). If you’ve ever noticed how node apps require in their dependencies and then use them, Webpack lets you do that exact same thing in the browser! This has a few benefits:

  1. You no longer have to maintain a concat task, or some other system to combine your modules in your build tool. Tell Webpack where to start its bundle, and it will “walk” or follow your dependencies tree and make sure it gets every referenced module.
  2. New features can be scoped to a smaller subset of files, which means your team can work on more things in parallel.
  3. Along with your own custom modules and packages, you can use just about any package from npm!

Using CommonJS (or modules) on the frontend opens up new ways to think about your JavaScript and project structure. For example, you could create a “manifest” file that simply requires in the various modules for your site, much like how we do with SCSS, to create a single file comprised of every referenced module.

Wait, What is This Madness?!

Let’s back up a step. What is Webpack? In short, it is a JavaScript build tool. Webpack calls itself a “module bundler,” and it is really good at following dependencies (e.g. App.js imports Nav.js, so Webpack grabs Nav.js and makes it available inside App.js). Yet, Webpack can also handle your stylesheets and markup templates thanks to its loader system.

And Loaders are?

Loaders are Webpack plugins and behave like Express middleware. Loaders take in the current file as a string, do some transformations (e.g. convert it to ES5), and then return the string back out. They are chainable, and they are awesome. You’ll notice the babel loader in the following examples: it simply runs the JS through babel, which allows us to write ES6+7 and serve ES5 to browsers. Loaders are like plugins in that you’ll find a lot of open-source loaders on npm, the Webpack site, and Github.

My favorite loader might be the script-loader. This loader helps integrate JS libraries that may not be CommonJS ready, like a lot of jQuery plugins. It just makes sure the library you pass in is available in the global context just like if you had included the file via a script tag (hurray for eval!).

So far, we’ve covered a few things:

  1. Modules are pretty great and are catnip for frontend developers.
  2. Webpack eats modules for breakfast.
  3. Webpack loaders are simply plugins that behave like middleware.

Now, it’s time to check out some actual Webpack configuration!

Simple Configuration

Here’s an example config file for Webpack.

var webpack = require('webpack');
module.exports = {
entry: [
'./components/Root.js'
],
output: {
path: __dirname,
filename: "app.js"
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015']
}
}
]
}
};
view raw webpack.config.js hosted with ❤ by GitHub

This config tells Webpack to start with the JS file Root.js and get all the dependencies it references. It also specifies the output file path and name in this case app.js. This configuration specifies a single loader, Babel. Any file that passes the test (ends in .js in this case) and isn’t in node_modules gets run through Babel before Webpack packages it up in a bundle. The query object reflects the new Babel 6 preset system. One last note: like Gulp, Webpack’s configuration is “just JavaScript,” meaning you can do whatever JavaScripty thing you want, e.g. use fs to get the right files.

Fancy(er) Configuration

Here’s a more complicated Webpack config.

var webpack = require('webpack');
var path = require('path');
var globby = require('globby');
var src = './specs/';
var componentPath = path.resolve('./src/components');
var out = './specs/build/';
var specs = {};
var files = globby.sync("./specs/**/*.js")
.filter((x) => {
var fileObj = path.parse(x);
return (fileObj.ext === '.js' && fileObj.dir.search('build|helpers') === -1);
})
.map((x) => {
return x.split('specs/')[1];
})
.forEach((x) => {
var name = path.basename(x, '.js');
//key value list of files to create
specs[name] = `./${x}`;
});
module.exports = {
context: path.join(__dirname, src),
entry: specs,
output: {
path: path.join(__dirname, out),
//creates file in specs/build dir that has same name as source file
filename: "[name].js"
},
resolve: {
root: componentPath,
},
resolveLoader: {
//point the loaders to the node_modules directory
root: path.join(__dirname, "node_modules")
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015']
}
}
]
}
};
view raw webpack.config.js hosted with ❤ by GitHub

This config dynamically creates spec files that contain the various components they’re testing. As you can see, we use regular JavaScript (in this case ES6-ified) on lines 9–22. We use the excellent globby package to get the spec files and kick the whole process off. We do some filtering to create an object that has the file name as the key and the path as the value. We feed this to Webpack as the entry for our build. We then use the Webpack entry and output APIs to create a processed and packaged JS file named after each key in our entry object (just about everything in Webpack is an object, which is handy).

One thing to note here: the resolve object. The resolve object tells Webpack where to look when it finds a user-created module (not a node_module). This removes the need to have nasty ../../../ chains in our import or require statements!

Integrate with an Existing Build System

Like I said earlier, not only can Webpack handle modules, but also CSS preprocessors and template tasks. Yet, Webpack isn’t so massive or invasive that it demands that you replace your entire build system with it. Webpack has the same characteristics as other good tools; you can use as much or as little of it as need be. To integrate with an existing JavaScript build system, there is a Grunt plugin, and integration with Gulp is well documented.

Webpack has a node API and a command line API. We’ve found that the command line API is a lightweight way to introduce Webpack to whatever existing process you have. We’ve used npm scripts or something like shell.js to call out to the command line API without drastically disrupting the existing build process. We’ve also found Webpack’s built-in watcher to be highly efficient compared to more generic file watchers due to its excellent caching system. We definitely recommended using the Webpack watcher if you’re integrating with an existing project.

Webpack all the Things

As a frontend dev, I’ve been guilty of turning to tooling to solve “problems,” which actually makes things more complex with no actual benefit. This isn’t the case with Webpack. Webpack harnesses the power of modules for the frontend. It provides a powerful system for managing the complexity of building and bundling frontend code that’s well worth the cost of adopting a new tool. If you’re interested and want to keep learning more, Webpack provides a listing of several tutorial articles to help get people started. I would specifically highlight Pete Hunt’s great README and David Fox-Powell’s Webpack article as places to start. So, give Webpack five minutes. I promise you, it won’t be a waste.