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…
Contents
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
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.
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
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!
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
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.
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”})
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?
Nice one! Very plain and basic, kind of what I needed right now.
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
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)
});
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’,
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.
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 ?
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 😀
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.
+1
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
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.
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.