Webpack for ngUpgrade, Part 1: Our First Webpack Config

Upgrading AngularJS
14 min readFeb 10, 2018

--

For a lot of teams, the most overwhelming part of upgrading a big AngularJS application is moving from the Old World — script tags, Gulp, or Grunt — to the New World: module bundling with Webpack. You’re using the latest version of AngularJS and you’ve swapped out your controllers with components, but now it’s time to attack the Godzilla terrorizing your Developer Townspeople: your build process.

Rare footage of Grunt terrorizing innocent developers

I want to re-frame how you think about this, because this is actually a really exciting step. This is where we cross the divide and go from the world of file paths and task runners to ES6 modules, TypeScript, and bundling all of our code and assets with Webpack (and yes, I say ES6 instead of ES2015, fight me). This is what’s truly going to get us ready for dropping in ngUpgrade and migrating to Angular.

Is it going to take work? Yes. Probably a lot of it. But moving to the future doesn’t just magically happen. It has to be brought to life by work. Your work. Isn’t that exciting?! You’re on the precipice of leaping into modern web app development. How often do you get to do that!? Enjoy it!

Don’t worry, though. For all my grandiose talk of leaping into the future, we’re actually going to take it one baby step at a time. By the end of this guide, you’ll have learned how to set up your first Webpack config file and how to convert and bundle your very first ES6 module.

(If the length of this guide freaks you out, just hop over to our course for step-by-step videos on this topic and even advanced Webpack tactics.)

Our Starting Point

Take a minute and clone our course sample project on GitHub (don’t forget to run npm install). I’ll reference different commits throughout this guide so you can see examples of everything we cover here. Checkout this commit to see our starting point:

git checkout d94ce314578f01bf7b690dcfa39d235c3c96859b

Here’s where our sample application is now:

  • Our files are organized by feature, not type.
  • We’re using npm for dependency management.
  • We’re using AngularJS 1.6.x (you need at least 1.5 for components).
  • We’ve converted all our controllers to components and all of our routes to use template instead of controller and templateUrl.

If you look at earlier commits for this application, you can see that I purposefully created it not using best practices. Most of the controllers were not wrapped in an IFFE or using the use strict statement. Most of them were still using $scope instead of controller as syntax. I did all of that on purpose because I don’t want you to think that your application has to be in some perfect starting place before you can gradually upgrade your application to the latest version of Angular. Upgrading in the real world is messy and that’s just fine.

However, it doesn’t make sense to add Webpack and switch over to TypeScript and ES6 modules until you’ve broken everything out by feature, converted your controllers to components, and ditched all your references to $scope. (If you’re lost on any of that, we cover it all step-by-step in the course.) Once you’re at that point in your application, it’s time to make the leap forward.

Let’s start by learning the core concepts of Webpack before we dive into the code.

Webpack in Plain English

Before we jump into adding Webpack to our sample project and converting everything to ES6 modules and TypeScript, I want to go over some of the core concepts of Webpack in a way that’s easy to understand. Webpack is a very different animal than Gulp, Grunt, or using regular script tags. If you’re sitting here already crazy overwhelmed by the learning curve of Webpack, don’t worry. I promise I’m going to make it clear for you.

From 😤😱😭 to 😍😍😍

When I first started trying to learn Webpack, I was really frustrated. I came from Gulp and it took me long enough just to learn Gulp and piping and streaming and all of that. When I finally felt like I got my head around it — BAM! Webpack came along and totally blew it all away. However, the more I dug into understanding Webpack, the more I started to love it.

This is partially because the Webpack community has produced a lot of good resources since when I first started trying to learn it:

I don’t claim to be at those folks’ level. They’re incredible and I couldn’t be here without them. I will say that I am just like you, except that I’ve spent lots of time — hundreds of hours — trying to understand Webpack and configure it for use with Angular and AngularJS. My goal is to distill down exactly what you need to get going quickly with Webpack. Hopefully you’ll learn to love it just as much as I do.

What is Webpack and Why Are We Using It?

So before we keep going, what is Webpack anyway? Well, Webpack is a module bundler. That’s what makes it different from Gulp or Grunt, which are called task runners. Webpack reads ES6 modules and other types of assets and bundles them all up into a single output. Now, it can actually split this up into different outputs, or you can use a bunch of different plugins to slice and dice it, but at the end of the day, Webpack is a module bundler.

Why are we using a module bundler instead of Gulp or Grunt for our build? Modules and module bundling have won out in the tooling wars of how to get your JavaScript from development to production. You’ll see why as you understand the power and ease of developing with modules instead of having to concatenate lots of files together. We can actually swap out modules live and update our code on the fly very quickly as we develop. And that’s only the start.

And why are we using Webpack and not something else? Well, the short answer is that the Angular CLI currently uses Webpack underneath the covers. The Angular CLI is a tool that’s been developed to help with new Angular development, and Webpack is kind of like the engine underneath it. In fact, if you create a new project using the CLI and then run ng eject, you’ll see a big Webpack config file spit out.

Webpack at 50,000 Feet

At a very high level, this is how Webpack works:

The Webpack Flow

There’s an app entry point that you can see over on the left and that entry point has a bunch of import statements that pull in all the different assets. Webpack then builds a dependency graph to understand what all you need to run your application. From there, those files get passed to different types of loaders and plugins.

The loaders take each file type and do something to it. So, for example, the ts-loader passes your TypeScript over to the compiler. Then, after each file’s been dealt with by the different loaders, all of that gets passed to the plugins, which do things like uglify or optimize the end result. Finally, all of that gets spit out into an output. For example, a file called “bundle.js.”

Loaders and Plugins: What’s the difference?

Just to further help you understand the difference between loaders and plugins, think of it this way:

Loaders do stuff to the imports, plugins do stuff to the end product.

Loaders will do pre-checks, linting, transpiling ES6, and compiling TypeScript. Loaders also do any sort of CSS pre-processing like processing SCSS or LESS. They can also handle things like images and fonts.

Plugins, on the other hand, do stuff at the end. For example, plugins optimize or minify CSS and JavaScript. Plugins can also extract CSS to separate files. When we’re running in development mode, Webpack keeps all of our CSS in our bundle. In production, we want to spit out all that CSS into a separate file, or maybe multiple files if there’s vendor CSS.

Plugins also split bundles up. Sometimes we want to have our application JavaScript separate from the JavaScript for libraries like Angular or jQuery. Lastly, plugins do those little things at the end that we do for production builds, like adding comments or bumping versions. Anything that Gulp or Grunt can do, Webpack can do, it’s just a matter of understanding where in the process they go.

Once you understand Webpack and wrap your head around its core concepts, you’re really going to love it. But that’s a lot of abstract definitions to hit you with at once, so let’s dig into the code and get started with using Webpack.

Let’s Dig in to Webpack

Adding Webpack to Our Project

Our very first step into the wacky world of Webpack is to add it to our dependencies. You can install Webpack globally, but it’s best to install it as a development dependency within your application in your package.json. That way, everybody on your team will be running the exact same version of Webpack as they develop. To do that, it’s just our old friend npm install:

npm install --save-dev webpack

You should see something like this in your package.json now:

"devDependencies": {
//other dependencies above
"webpack": "^3.10.0"
}

It should be noted that we’ll be using Webpack 3 in this guide. At the time of publication, Webpack 4 is in beta, but promises to be awesome. For real world stuff, we’re going to stick with version 3 for now.

Our First Webpack Config

We could run Webpack just from the command line and pass in a bunch of configuration options. Webpack has a binary file and a CLI for passing in commands, and npm is just going to execute that binary. However, a better way to do this for larger applications is to make a config file.

First, we’ll make a file called webpack.config.js in our public folder. The Webpack config file exports an object and this object has all the properties we’ve talked about: entry, output, loaders and plugins. It can also have others as well, but Webpack uses this configuration file to process your application using all the loaders, plugins, and settings that you’ve specified in this object. The simplest thing we could do right now is just create our module.exports and set it equal to an object:

module.exports = {
}

Let’s specify our entry point first. It takes a string and our entry will just be ./app.js:

module.exports = {
entry: './app.js'
}

Now, we’ll specify our output. This is an object,and we’re just going to do the very simplest thing and use the filename option:

module.exports = {
entry: './app.js',
output: {
filename: './dist/bundle.js'
}
}

We’re going to make a dist folder and output to a file called bundle.js. Later on, we’ll worry about splitting up our app files from our vendor files and being more complicated with our build process, but for now, we’re just trying to see this work in the simplest possible way.

In fact, you can see what happens when we run Webpack right now. Open your terminal (ctrl-` in Visual Studio Code), make sure you’re in the same folder as our package.json, and just simply type webpack to run the binary. It will know to pull in the nearest webpack.config.js file that it sees. You can see the output has a hash and a version and it tells us that it emitted this bundle.js file:

Our very first bundle!

Sure enough, we now have a dist folder and inside that dist folder is our bundle.js.

If you openbundle.js, you see a whole bunch of extra code:

Webpack’s bootstrap code (about 68 lines at the moment)

This is the Webpack bootstrap code that lets us run modules in the browser. If you scroll all the way down to the bottom, though, you can see our application code:

Hey look! It’s our app!

We’ve got our app and our config, which are in our app.js entry. It’s like magic, isn’t it? That’s all that’s in there right now because we haven’t actually started converting to ES6 modules and doing all of our importing and exporting. You don’t even see AngularJS in this file yet because we haven’t imported it. BUT we’ve successfully generated our first bundle with Webpack.

Isn’t that exciting?

Adding the Bundle to index.html

Now that we’ve got our first bundle generating, let’s add bundle.js to our index file. You’ll notice that this sample app is just using plain old script tags like back in the day. Let’s be a little clever swap it in our app.js script tag to see if it’s working right:

<script src="./dist/bundle.js"></script> //formerly app.js

Go ahead run the application (you can see how to set up and run static-server in the readme) and open up localhost:9080. Click around and make sure it’s all still working. If you open up the network tab in developer tools, you can see that it’s loaded our bundle.js file, but not our app.js file:

Our app.js file is now hidden in our bundle. Neat!

So that’s cool, and I can move around to the other routes just fine. Awesome. Now, let’s convert our first component to a module and see what happens.

Our First ES6 Module

Let’s open our home component JavaScript file so I can show you just how easy it is to convert it to our first ES6 module. It looks like this right now:

There are two steps to converting it to a module:

  1. Export the component out of the file
  2. Move the component registration from this file into app.js

Export the Component

Let’s first get rid of our IFFE surrounding our code and the use strict statement (ES6 modules run in strict mode by default). Then we can export the homeComponent object. There are two types of exports for ES6 modules. There’s named exports, in which case we would just export our homeComponent as a named object and import it as such. Or, if you’re only exporting one function or object in a file, you can just export that with the default keyword. So after our function and I’m going to type:

export default homeComponent;

In AngularJS, whether you use named exports or default exports is just a matter of preference. Angular (2+) uses named exports, and maybe that’s why, personally, I use export default in my AngularJS components. It helps me keep things organized. Of course, if we were exporting multiple things from this file, we’d have to use named exports, but with our components we’re just exporting that one object, so I stick to export default.

Our file looks like this right now:

Move the Registration

Now, let’s cut the component registration (the .component() part) from the end and delete the angular.module reference. Then, we can hop over to app.js (ctrl-p and type “app.js” in Visual Studio Code) so we import it.

Before you forget, paste in the component registration after our module declaration so that we have this:

angular.module('app', ['ngRoute'])  
.component('home', homeComponent);

Unfortunately, app.js has no idea what homeComponent is. It doesn’t just magically know what exports are available. So, at the top of the file, we’ll import our homeComponent:

import homeComponent from './home/home'; 

(With imports, you don’t actually need the file extension at the end of the string, it’s just assumed by convention.)

With this homeComponent that we’ve imported here, the import didn’t actually pull in that name of homeComponent from home.js. This is actually shorthand for import default as homeComponent. That gets wordy over time, so we can just shortcut that and use the syntax above.

The last thing we need to is hop over to index.html and remove our reference to home.js on line 20. Your finished app.js will look like this:

You can also see the current state of app.js, home.js, and index.html in this commit.

Run Webpack One More Time

The last thing we need to do is run Webpack to check our work. Open the terminal and type webpack and you can see in the output that it pulled in home.js:

Our home component is now included in the bundle.

Let’s go look at the bundle and see what it looks like after all the Webpack code:

Our bundled home component.

Sure enough, there on line 84, you can see the start of our homeComponent (it looks a little funky due to Webpack’s processing). And, just above that you can see the registration of the component underneath our application declaration:

app.js

Let’s take one last peek at the browser and just see if it’s actually working like this. Since the server is running, just do a hard refresh and you can see that everything is still working. If you open up your bundle file in the dev tools, you can see everything that we just looked at in the code. You can also see that the home.js file is no where to be found in the network tab, so Webpack is successfully bundling this up, and it’s running the module system inside of the browser.

Where to Go Next

I’ve shown you how to set up your very first super simple Webpack build. Now, this simple build is great for when we’re just developing and running in Chrome and working in the browser. But if we want to take advantage of all the new innovations of ES6 or TypeScript (which we do) or if we need to optimize this application for a real production environment (which we must do to keep our jobs), we’re going to need to go further with Webpack by introducing loaders, plugins, and different configurations for different environments.

I’ve also shown you how to start converting your JavaScript files to ES6 modules. You also might think that the next step of your upgrade process would be to go ahead and convert all the rest of your components in your application to these ES6 modules, but hold off. If you’re on the road to ngUpgrade, it’s better to get TypeScript set up first and then come back to converting to modules as you also swap to TypeScript. That way you’re only touching each file only once.

If you love this guide, I’ve got 200+ detailed videos, quiz questions, and more for you in my comprehensive course UpgradingAngularJS.com. I created it for everyday, normal developers and it’s the best ngUpgrade resource on the planet. Head on over and sign up for our email list to get yourself a free Upgrade Roadmap Checklist so you don’t lose track of your upgrade prep. And, while you’re there, check out our full demo.

--

--

Upgrading AngularJS

The best ngUpgrade resource on the planet. Comprehensive, step by step course to help you move from AngularJS to Angular.