
Write Better JavaScript With Webpack
I get it. The JavaScript ecosystem is exhausting. There exists an abundance of tools and frameworks, and each one wants to rescue you from the shortcomings of its peers. Attempting to learn and keep up with these tools takes time and energy that you would rather devote to your project. Reducing cognitive overhead is a core component of Forestry.io’s philosophy — we designed a static site CMS that is easy to set up and use. A good tool shouldn’t get in your way!
The Problem
Writing complex client-side code is painful without some help. You already know that a well-organized codebase is easier to understand and maintain, but that can be difficult to accomplish when you’re just throwing a bunch of files in <script>
tags. On top of that, best practices dictate that all of your code should be served up in a single file.
Many solutions in this space, such as Gulp, can be used to smash a bunch of JavaScript files together into a single bundle. This is a great improvement — but is it really enough? We’re left with a linear dependency chain and relying on global variables to share information between our separate files.
A Better Way
This post will explore why Webpack is the right choice for bundling your JavaScript application. If you’ve never used a build tool for your frontend assets, Webpack is a great place to start. If you’re already comfortable using a task runner like Gulp, delegating the asset bundling to Webpack will provide you with a more sophisticated organization and dependency management strategy.
Webpack helps you write better code and gets out of your way so you can focus on making great software.
Webpack vs. Gulp
Maybe you’ve heard this before: Gulp is a task runner, Webpack is a module bundler. What does that actually mean?
Gulp is a great automation tool. It allows developers to set up complex file processing pipelines using JavaScript, and run them as simple commands. However, when it comes to packaging your JavaScript application, the previously-mentioned file smashing is about as far as it can go. If code in file A depends on code in file B, you need to tell Gulp to include file A first. This can be done directly in the Gulp configuration, or by maintaining a separate manifest file for use with a plugin like asset-builder. As you add more files and your dependency tree grows, this can become a chore to maintain.

Complex dependency resolution is Webpack’s specialty. You specify a file that serves as your application’s entry point, and Webpack will recursively process module imports in your code to build a dependency graph. When it’s all done, Webpack emits your JavaScript in a single bundle file. There’s no need to tell Webpack the order in which it should put your files together; it figures it out by reading your code.
Gulp’s dependency resolution capability is limited and requires you to explicitly define your dependency chain. Webpack uses the dependency logic built into your code.
Writing Modular Code
In order for Webpack to work its magic, your code needs to be written as JavaScript Modules. A JavaScript Module is a single JavaScript file that exports pieces of functionality for use in other parts of the code. Code in other files can then import this module in order to use it.
Webpack supports two standards for Module configuration: EcmaScript and CommonJS. The following examples demonstrate how you would configure a module defined in hello.js
to be loaded by a file named app.js
in each of these standards:
EcmaScript Module Syntax
hello.js
export function hello() {
console.log('Hello World!');
};
app.js
import {hello} from './hello';
hello();
The EcmaScript standard uses the export
and import
keywords.
CommonJS Module Syntax
hello.js
module.exports = function() {
console.log('Hello World!');
};
app.js
const hello = require('./hello');
hello();
The CommonJS standard works by assigning the exported code to module.exports
and importing it by calling the require()
function.
require()
to import them into your code.
Intuitive Dependency Resolution
Webpack’s dependency resolution is intuitive because you don’t need to look at the Webpack configuration to understand what’s going on. You only need to know where the entry point is, and this will usually be obvious. This is helpful for new contributors to your project: they can understand the relationships in your code without having to understand the details of your build configuration.
Configuring Webpack
Let’s set up the simplest possible configuration to understand the core components of Webpack. Start by setting up the following file structure:
src/
app.js
webpack.config.js
By default, Webpack looks for configuration in a file called webpack.config.js
. Next, let’s install the Webpack modules by running the following terminal commands:
npm init -y
npm install --save-dev webpack webpack-cli
Now that Webpack is installed in the project, open up webpack.config.js
in a text editor and add the following code:
var path = require('path');
module.exports = {
entry: './src/app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
};
That’s all you need for a bare-bones Webpack config: define the entry point and the output file. Webpack requires an absolute path for the output, so we need to use the path
module to resolve this at runtime.
We can then run Webpack by invoking the CLI command:
./node_modules/webpack-cli/bin/webpack.js
This will read src/app.js
and compile it into dist/bundle.js
. Once the command completes, our project should look like this:
dist/
bundle.js
src/
app.js
package.json
webpack.config.js
Using NPM Scripts
A common strategy for streamlining your build CLI is to add custom scripts to the scripts
property in your package.json
file. You can then invoke these scripts with npm run SCRIPT_NAME
.
Open your package.json
file in a text editor and locate the scripts
section:
{
"name": "my-webpack-demo",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
},
…
}
The default package.json
comes with one script called test
that just returns an error message. Let’s add two more:
{
"name": "my-webpack-demo",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"watch": "webpack --watch"
},
…
}
Now we can run our Webpack build by simply calling npm run build
. We have also added a watch
script that passes the --watch
flag to webpack-cli
. When invoked with this flag, Webpack will watch our files and automatically run the build when changes are detected.
Congratulations! You are now writing modular JavaScript, and you have a build process that puts it all together for you.
Next Steps
We have only scratched the surface here: Webpack is capable of much more than this, but bundling JavaScript modules is its core feature. Next time, we will explore Webpack’s loaders and plugins in order to optimize your JavaScript bundle, and using the development server for live reloading of your browser. If you want to get started right away, go ahead and dive into the Webpack documentation.
Join us every Friday 📅
Frontend Friday is a weekly series where we write in-depth posts about modern web development.
Next week: We’ll compare the usability and features of Hugo and Jekyll to help you decide which static site generator is right for you
Last week: We looked into CI/CD: Automating Your Static Site Deployment with CircleCI.
Have something to add?
Caught a mistake or want to contribute to the blog? Edit this page on Github!