LearnBundling Angular with webpack

Avatar

Ken Howard
writes on April 19, 2016

I love building Angular applications. Because Angular is so modular you can separate your JavaScript code like your controllers and services into multiple files. But adding all of the script references to my HTML file is painful. If only there were some way to use a module loader similar to how modules work in Node…

webpack To The Rescue

webpack is a module loader that works similar to how Node handles modules.

With webpack you can install Angular using NPM, the Node Package Manager.

NPM ships with Node and has become the _defacto_ package manager for front-end JavaScript libraries and frameworks.

In this tutorial, you’ll update an existing Angular application to use CommonJS modules and bundle it using webpack.

I won’t go into why you should use webpack, you can read the motivation behind the project and come to your own conclusions.

Here’s how the starter project is structured:


project/
--app/
----controllers/
------dashboard.controller.js
----directives/
------yep-nope.directive.js
----services/
------github-st
atus.service.js
----app.js
--js/
----angular.min.js
--index.html

Your Angular application is pretty small right now, but you’ll need to update it to use a module loader like webpack so it’s ready for the future. webpack has a wide variety of use cases and configuration options. You’ll be using a very small subset of webpack’s capabilities in this tutorial.

Be Prepared

Before you start you must have Node installed on your machine. You can download Node from nodejs.org. Once Node is installed you’ll be able to follow this tutorial.

You’ll also need to download the starter project for this tutorial.

Review Application

Go ahead and unzip the application and open index.html in your favorite browser. You should see something like this.

See the Pen Angular webpack Starter by Ken Howard (@kenhowardpdx) on CodePen.

Let’s get started migrating this Angular application for use with webpack.

Step One

Update index.html Open the project in your favorite editor. We’ll be bundling our application into two primary files (app.bundle.js and vendor.bundle.js). Bundling is the process of joining multiple files into a single file. In our case, we’ll be bundling all of our application’s code into app.bundle.js. Third party libraries like Angular and other dependencies will be bundled into vendor.bundle.js. This keeps the vendor code separate from our code and makes debugging our application less of a hassle. Take a look at the index.html file. !doctype html Dashboard

Is GitHub Up?

You’ll notice there are more than a few JavaScript files referenced. You’ll need to replace these references with the bundled files you’re about to create. The order you reference these scripts _is_ important because the browser executes them as they are loaded. And because our application depends on Angular, the vendor.bundle.js script needs to load first. <script src=”js/vendor.bundle.js”></script><script src=”js/app.bundle.js”></script>

Great. Your index file is ready. Next you’ll need to configure webpack to create the bundles.

Step Two: Install Dependencies

The downloaded project included _angular.min.js_ in the _js_ directory. In this step you’ll install Angular using NPM so the included Angular library is no longer needed. Delete _angular.min.js_ from the _js_ directory before you continue.

Open a terminal and navigate to the root of your application.

» cd ~/path-to-the-files

Before you install webpack or any of your third party libraries like Angular you’ll need to initialize your project with NPM.

You can do this by simply executing this command in your terminal window:

» npm init -y

The -y flag bypasses the configuration wizard and accepts all of the default configuration options. Omit the -y flag if you’d prefer to use the configuration wizard instead.

Now install your dependencies. At this point, our application only needs Angular and webpack.

» npm install angular webpack --save-dev

If you are familiar with NPM you’ve probably noticed that we’re using the --save-dev flag here instead of the --save flag. It is up to you and your build process on how you install dependencies. I recommend only using --save when a server-side application requires third-party modules at run-time. Otherwise use the --save-dev flag. In the case of this tutorial, Angular and webpack are part of a build process so you should use the --save-dev flag.

Great. You’ve got Angular and webpack installed!

Step Three: Configuring webpack

webpack needs to know where your application files live and where you want to store the bundles. To do this it looks for the webpack.config.js file in the root of your project. This file informs webpack on how you want your application bundles separated and if you have any pre-processing tasks to perform.

Create webpack.config.js in the root of your project.

var webpack = require('webpack');
module.exports = {
context: __dirname + '/app',
entry: {
app: './app.js',
vendor: ['angular']
},
output: {
path: __dirname + '/js',
filename: 'app.bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js")
]
};

This file needs to export a configuration definition that webpack can interprit. The key properties here are context, entry, and output.

context: This is an absolute path to your application’s source files.
entry: The main file that bootstraps your Angular application. In our case we passed an object with keys of app and vendor. This generates multiple bundle files.
output: An object that configures where the bundle files are saved once generated.

There’s also a plugins property. The only plugin we need is built into webpack. It allows you to split your application’s code and your third party code into separate files. You can read more about code splitting here].

Good work so far. You’ve got webpack installed and configured. Next you’ll edit your Angular application to use CommonJS modules.

Step Four: Require Modules

If webpack were to bundle your code at this point it would only bundle the app.js file that is referenced in your webpack.config.js files. This is because your app.js file hasn’t required any additional files.

To include _yep-nope.controller.js_, _github-status.service.js_, and _dashboard.controller.js_ you’ll need to require them within your app.js file.

You could simply require these files at the bottom of app.js.

// app.js
...
require('./directives/yep-nope.controller');
require('./services/github-status.service');
require('./controllers/dashboard.controller');

This works well for small applications, but it could get out of hand with larger applications.

Instead of simply requiring these files create a new file called index.js in the controllers folder. This file will act as the entry point for all of your application’s controllers.

This file should require Angular and your _dashboard.controller.js_ file/.

'use strict';

var angular = require('angular');

angular.module('dashboard').controller('dashboardController', require('./dashboard.controller'));

Be sure to remove the controller definition line from _dashboard.controller.js_.

Now do the same for the directives directory.

Create the index.js which will act as the entry point for all your application’s directives.

This file should require Angular and your _yep-nope.directive.js_ file.

'use strict';

var angular = require('angular');

angular.module('dashboard').directive('yepNope', require('./yep-nope.directive'));

Remove the directive definition line from _yep-nope.directive.js_.

Next create an index.js file in the services folder. This file will act as the entry point for all of your services.

This file should require Angular and your _github-status.service_ file.

'use strict';

var angular = require(‘angular’);

angular.module(‘dashboard’).service(‘GithubStatusService’, require(‘./github-status.service’));

Now in _github-status.service.js_ remove the service definition at the bottom of the file and add a module.exports definition and export the _GithubStatusService_ function.

The last line of the file should look like this

module.exports = GithubStatusService;

The final piece of the puzzle is to require these new files in your app.js file

// app.js
...
require('./directives');
require('./services');
require('./controllers');

When your application grows and you need to add new directives, services, and controllers you’ll need to create the Angular definitions in _directives/index.js_, _services/index.js_, or _controllers/index.js_ before they’ll be included in your bundle.

Your Angular application is ready to go. Next you’ll configure NPM to bundle your code using webpack.

Step Five: Configuring Tasks

When you initialized the project with the npm init command at the beginning of this tutorial you might have noticed a file named package.json was in your project’s root directory. The package.json is responsible for keeping track of your project’s dependencies. It also provides a way of running various tasks via the scripts option. When you create a script Node can execute your project’s dependencies. We’re going to add a bundle task to bundle our code.

Open package.json in your text editor.

Add a new line below the test script. Call the task “bundle” and have it execute “webpack”. The scripts block should look something like this:


"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"bundle": "webpack"
},

In your terminal execute npm run bundle<c/ode>. The terminal should output something like this


» npm run bundle

angular-webpack@1.0.0 bundle /Users/ken/Projects/angular-webpack
webpack

Hash: fa17ee9ecec0b19a73ae
Version: webpack 1.12.12
Time: 550ms
Asset Size Chunks Chunk Names
app.bundle.js 1.42 kB 0 [emitted] app
vendor.bundle.js 1.12 MB 1 [emitted] vendor
[0] ./app.js 82 bytes {0} [built]
[0] multi vendor 28 bytes {1} [built]
[1] ./services/index.js 145 bytes {0} [built]
[4] ./services/github-status.service.js 373 bytes {0} [built]
[5] ./controllers/index.js 147 bytes {0} [built]
[6] ./controllers/dashboard.controller.js 278 bytes {0} [built]
+ 2 hidden modules

Now run the application in the browser.

» open index.html

You did it! Your Angular application is up and running using webpack.

ES6 Modules

In this tutorial you’ve learned how to migrate a simple Angular application for use with webpack using the standard exports/require CommonJS module pattern. While this works in practice today, more and more application developers are making the leap to ES6 module loading and transpiling their code using tools like Babel and TypeScript.

If you are interested in developing your application using ES6 modules you should take a look at the babel-loader and ts-loader plugins for use with webpack.

Get Coding

You’re ready to start building your own applications using Angular and webpack. If you have questions or hit any stumbling blocks be sure to leave a comment below. We’re all in this together!

You can download the final project files of this tutorial to compare them with your copy.


 

Want to learn more from Ken? Check out his newest course on Treehouse, Building a MEAN Application.

19 Responses to “Bundling Angular with webpack”

  1. Pragya on July 12, 2017 at 2:26 am said:

    I am using your final project and when i run it, it was running, but when i run the webpack command , it run successfully but when i open the index.html . I found such errors in console.
    1)vendor.bundle.js:14808 TypeError: gh.getStatus(…).success is not a function
    2)vendor.bundle.js:14808 Error: [$sce:insecurl] Blocked loading resource from url not allowed by $sceDelegate policy

    • Jordan Bisasky on January 28, 2018 at 8:16 pm said:

      Same issue. I fixed it by locking down the versions of Angular and Webpack in package.json.

      In package.json change….
      “angular”: “^1.5.0”,
      “webpack”: “^1.12.14”

      to…
      “angular”: “1.5.0”,
      “webpack”: “1.12.14”
      …removing the ^’s. Then delete the node_modules folder. Run npm install again, then npm run bundle, and open index.html. Works!

  2. Great article!
    Just wanted to remind you about the new naming guidelines for Angular:

    – Use “Angular” for versions 2.0.0 and later (e.g. “I’m an Angular developer

  3. i want to include routes file in angular controller (js file) and at the same time i want to use angular controller (js file) in node routes. Is it possible.

  4. Looks like the syntax you’ve used for the CommonsChunkPlugin is now considered deprecated:

    Error: Deprecation notice: CommonsChunkPlugin now only takes a single argument. Either an options
    object *or* the name of the chunk.
    Example: if your old code looked like this:
    new webpack.optimize.CommonsChunkPlugin(‘vendor’, ‘vendor.bundle.js’)
    You would change it to:
    new webpack.optimize.CommonsChunkPlugin({ name: ‘vendor’, filename: ‘vendor.bundle.js’ })
    The available options are:
    name: string
    names: string[]
    filename: string
    minChunks: number
    chunks: string[]
    children: boolean
    async: boolean
    minSize: number

    Updating that line in webpack.config.js to this, fixes this message:

    new webpack.optimize.CommonsChunkPlugin({name:”vendor”, filename:”vendor.bundle.js”})

  5. Tim Grafton on March 2, 2017 at 5:18 pm said:

    Hmm…I get the error ‘window is not defined’ when trying to bundle angular in node. I’ve tried JSDom to no avail. Any advice?

  6. Nice one! Very plain and basic, kind of what I needed right now.

  7. Dan Marquette on January 21, 2017 at 2:09 pm said:

    Just came across this as I strive to learn several module loaders. Aside from the errors noted above, this has helped me out. Even the troubleshooting of the author’s errors, and an occasional copy/paste blunder (double quotes don’t paste well).

    There is one other edit to be made if you are using a newer version of angular. I’m on angular 1.6.1 and they have finally removed the .success() function from a service call.

    So in the DashboardController you’ll need to change:

    gh.getStatus().success(function(status) {
    _this.github = status;
    });

    to the now used .then():

    gh.getStatus().then(function(status) {
    _this.github = status;
    });

    I am also troubleshooting a $sce error when running locally, but I suspect that may be an issue on my end.

    Cheers,
    Dan

    • Simon on July 4, 2017 at 1:34 am said:

      For anyone trying to debug the SCE error, in app/app.js the sce error detection can be turned off:

      angular.module(‘dashboard’, []).config(function($sceProvider) {
      $sceProvider.enabled(false)
      });

      • Simon on July 4, 2017 at 1:37 am said:

        There will also be an $http:badjsonp in the latest AngularJS that can be fixed by removing the callback parameter from the URL in the service:

        url: ‘https://status.github.com/api/status.json’,

  8. Jon Biere on October 4, 2016 at 1:35 pm said:

    Why do we need to require(‘angular’) in the index.js files when creating the directive, controller, and service CommonJs modules? Won’t angular already be referenced in the vender.bundle.js? And then angular will be available as a global as usual.

  9. Hi! thanks for the post, helped me getting started with webpack and angular.

    I have a question here.
    The vendor.build.js files includes the full angular.js version instead of angular.min.js . How can I configure so as to include the minified version instead of the full version of the libraries , so that the vendors.build.js can be kept as small as possible ?

  10. David Retana on August 8, 2016 at 3:31 pm said:

    There are many but little typing errors but maybe those mistakes were made intentionally for the reader to put working his gray matter.

    A simple good one intro to webpack monster 😀

  11. Dan Hodson on June 20, 2016 at 3:18 am said:

    This is a good tutorial, and really helped me get up and running with Webpack and Angular better than any other tutorial I have seen on the web so far.

    The article itself really needs to be checked though for formatting errors. Quotes and single quotes in rich text rather than useable code. Sections of code formatted as Rich text rather than code. Incorrect filenames: _directives/index.js_ there are no _’s in the downloadable example. Same with other file names.

    Also I couldn’t get this working until I compared it with the downloadable finished example. This is because it does not instruct using module.exports on the controller or directive, only the service. It worked once I included:

    // in dashboard.controller.js
    module.exports = DashboardController;

    // in yep-nope.directive.js
    module.exports = YepNopeDirective;

    Once these were included it did work. This may be what you need @MiguelPalau. Again good article, but needs a tidy up to fix this stuff.

  12. nice article,Webpack is a module bundler for the web. It is incredibly powerful and enables modularity in angular applications. This is the first of several lessons to get you up and going with webpack in Angular applications

  13. I tried to follow this tutorial but in the end got no results, I don’t know what’s wrong I already tried 2 times from scratch and still nothing.

    • Dan Hodson on June 20, 2016 at 3:19 am said:

      See my comment above, may help you fix your example.

      • Dan, I followed your directions. Thank you. Those changes fixed some errors building the pages and in the console. However, it does not appear to work. I see no errors in the browser console nor any html. A blank page is all I see now.

Learning to code can be fun!

Get started today with a free trial and discover why thousands of students are choosing Treehouse to learn about web development, design, and business.

Learn more