What every Angular project likely needs – and a Gulp build to provide it
Starting an AngularJs project also means choosing a whole toolchain that goes along with Angular. In this blog post we will propose a set of tools that a new Angular project will likely need, and present a simple Gulp build that provides those features.
Let’s go over the following topics:
- Why Gulp? Goals for a baseline Angular build
- Why use a CSS pre-processor, and why Sass
- The advantages of a module system, and why browserify
- Pre-populating the Angular template cache
- Angular-friendly Javascript minification
- Reducing the number of HTTP requests using sprites
- Setup a development web server
- Running tests with Karma, code quality with JSHint, and more
Why choose Gulp?
Although there are other task runners out there, it seems that Gulp is more and more the solution of choice, because it provides the following features:
- it’s super fast as it’s build for concurrency from the start
- it provides a simple to learn API (based on a pipes and filters architecture) that produces some very readable and maintainable code.
Goals of a good Angular build
Ideally, the build for an Angular project would:
- be of low complexity, ideally not much more than 100 lines of code
- be lightning fast, in the order of a few seconds
- imply no dependencies to non-node tool chains
- be exactly the same in both development and production
- solve some Angular-specific issues: Angular-friendly Javascript minification and template cache pre-population
- provide a development mode for developer productivity
- allow to run tests and do code quality checks
- minimize the number of HTTP requests needed in production, via CSS and Javascript bundling, as well as images sprites
A working build and sample App
In order to make it more concrete, here is the build and applied it to a sample app: the TODO App from the TODO MVC project. You can try out the sample app just bellow, (or here):
The build and sample app are also available in this Github repository.
In the next following sections we will go over a proposed toolchain for Angular projects, and walk through the Gulp build that provides those features.
Why use a CSS pre-processor, and why Sass
To quote the yearly Thoughworks Technology radar from last year:
We believe that the days of handwritten CSS, for anything apart from trivial work, are over.
The limitations of plain CSS are plenty and make it hard to write maintainable stylesheets. Although new CSS3 standards are addressing many of the limitations, widespread adoption takes time.
CSS enhanced features
In order to produce maintainable CSS, we really need today:
- A CSS bundling mechanism
- A partials mechanism, that allows to split CSS in logical chunks, without generating additional http requests
- the possibility to define CSS variables of limited scope
- the possibility to embedded related styles in a tree structure
- the ability to define CSS ‘functions’
All these features are provided by the multiple CSS pre-processors available today, the difficulty being how to choose one. The two main ones are Sass and Less.
Many libraries are built on top of these pre-processors that encapsulate commonly used patterns, see for example the Sass Compass library.
Sass or Less?
The Less pre-processor would seem to be a better choice for a node-based build, as its also a node-based tool.
But today Sass is not tied to the Ruby toolchain anymore, as there is a C implementation of Sass (libsass) that allows for fast Sass compilation in a node environment, via plugins like gulp-sass.
Also, the industry seems to be converging around Sass: see for example this post on css-tricks. In the end, the three main reasons that I decided to opt for Sass where:
- good integration with node via libsass, no need to install Ruby
- knowing that the most used Sass library Compass is being ported to libsass for Compass 2, see this Github issue
- the fact that Angular Material Design is based on Sass, so we are likely to find it again in the future
The build-css task
Sass is integrated in the gulp build via the build-css
task bellow:
gulp.task('build-css', ['clean'], function() { return gulp.src('./styles/*') .pipe(sourcemaps.init()) .pipe(sass()) .pipe(cachebust.resources()) .pipe(sourcemaps.write('./maps')) .pipe(gulp.dest('./dist')); });
There is support for source maps, so it will be possible to debug the CSS in the browser and have it refer to the original Sass sources. More on the cache busting feature later.
Why use a module system
There are several reasons why a module system is likely essential for any non-trivial Javascript project:
- Adding all the Javascript dependencies in script tags at the bottom of the multiple html pages quickly falls apart as the codebase and the team increases
- and what about concatenation and minification? repeating the content of the script tags in concatenation tasks at the level of the build quickly becomes unmanageable
Besides solving those common problems out of the box, using a module system encourages:
- the development of well isolated small modules with clearly defined boundaries and dependencies
- structuring the code in small chunks that are on its own easy to understand
- simplify and better enable testing
But the industry is very fragmented in what concerns module systems: there is requireJs, CommonJs, browserify, ES6 modules to name but a few. So which one to choose?
Why CommonJs is appealing
CommonJs is the same module system used by node in general. It is very simple to understand and use. If we need a module, say for example express, we simply say:
var express = require('express');
Importing a module is synchronous and very intuitive. The main advantage of this syntax is that there is no need to define a config file where all the dependencies are declared: we only need to point to the entry file, and the require
calls will themselves implicitly document the dependency tree of Javascript files.
CommonJs used to be a backend-only module system, until browserify came along, allowing us to use the same familiar syntax also on the frontend.
Angular now officially supports browserify
Recently in the Angular release 1.3.14 instantaneous-browserification the Angular team added improved support for browserify.
Namely it published in npm all Angular files as CommonJs modules, giving them meaningful exports that allow for simplified browserify usage.
Although ES6 modules will be the solution of choice for Angular 2, it seems that browserify is the recommended solution for Angular 1 projects.
The build-js Gulp task
Going back to our build, the following is the task that builds the Javascript bundle of our application:
gulp.task('build-js', ['clean'], function() { var b = browserify({ entries: './js/app.js', debug: true, paths: ['./js/controllers', './js/services', './js/directives'], transform: [ngAnnotate] }); return b.bundle() .pipe(source('bundle.js')) .pipe(buffer()) .pipe(cachebust.resources()) .pipe(sourcemaps.init({loadMaps: true})) .pipe(uglify()) .on('error', gutil.log) .pipe(sourcemaps.write('./')) .pipe(gulp.dest('./dist/js/')); });
Why not use the gulp-browserify plugin
Note that browserify is used directly and not via some plugin like gulp-browserify. This is because the gulp team has blacklisted several plugins such as gulp-browserify, as they make for separate build processes that are hard to integrate well with other gulp plugins.
Instead the gulp team provided a recipe of how to use browserify: we simply call it directly, and pipe its output which includes the concatenated js files wrapped in modules and stream it to other gulp tasks.
The Gulp recipe was adapted to fix one Angular-specific problem: minification support.
Angular-friendly Javascript minification
Angular is well-known for needing an extra build step before minification in order to support its dependency injection mechanism. Take for example this code line:
angular.factory('todoStorage', function ($http, ) { ...
If we don’t take any preventive measure, this will get minified to something like:
a.factory('todoStorage', function (h, ) { ...
The problem is that the variable name $http
is lost, so Angular can no longer inject it by finding the $httpProvider
that it identifies by concatenating the variable name $http
with Provider
. This causes the application to break if minified.
Before minification, the above line needs to be rewritten to an alternative array syntax that preserves the string names but is a bit more verbose:
angular.factory('todoStorage', ['$http', function ($http, ) { ... } ]
Fixing the Angular minification problem
One way to fix it is to simply use the array syntax everywhere, as this is still readable and avoids an extra build step.
Alternatively, the browserify transform browserify-annotate
was included in the build-js
task above to solve this:
var ngAnnotate = require('browserify-ngannotate');
Note that this transform applies the array syntax in almost all places, but I still had to apply it manually to route definition resolve
steps, see the file app.js
.
Pre-populating the Angular template cache
Another Angular-specific problem that a build needs to solve is to pre-populate the Angular template cache.
If we don’t do this, then every template of every directive will result in a separate HTTP request, resulting in an unacceptably slow application startup time, as the application needs to wait for the templates to be loaded before rendering the page.
Reducing the number of HTTP requests using sprites
Many of the optimizations that the build provides revolve around reducing the number of HTTP requests needed to bootstrap the app. And what about images, such as for example icons?
One way to reduce the number of HTTP requests related to images is to group them together in an image sprite, and load only that. The following section of the build uses the gulp.spritesmith
plugin to generate sprites:
gulp.task('sprite', function () { var spriteData = gulp.src('./images/*.png') .pipe(spritesmith({ imgName: 'todo-sprite.png', cssName: '_todo-sprite.scss', algorithm: 'top-down', padding: 5 })); spriteData.css.pipe(gulp.dest('./dist')); spriteData.img.pipe(gulp.dest('./dist')) });
All the png
images inside the images
folder get converted into one single todo-sprite.png
file. In order to be able to use the sprites, a Sass mixin is generated in file _todo-sprite.scss
. The mixin can be used in the following way:
@include sprites($spritesheet-sprites);
This will create a set of CSS classes with the same names as the image files, which allow to use the different images. For example the image
cal-right-button.jpg
could be used in the following way:
<span class="cal-right-button"></span>
Why use cache busting?
A convenient practice that comes in handy in both development and production is to prevent the browser from keeping stale copies of the
CSS / Javascript of the application.
This prevents customers from viewing a stale version of the application that no longer matches the Html, and avoids bug reports that are sometimes hard to link back to the presence of stale resources.
The most effective way to achieve cache busting is to append to each CSS/Javascript file a hash of its content. This way when the file changes, the file name also changes and the browser will load the latest version of the resource.
Implementing cache busting with gulp-cachebust
The plugin gulp-cachebust was used to implement the cache busting functionality. We have seen in the build-css
and build-js
tasks a call like this:
.pipe(cachebust.resources())
This call keeps track of the CSS and Javascript files that will need to have their names concatenated with the file hash. The actual replacement of the css file names is done in the build task, by calling
cachebust.references()
:
gulp.task('build', [ 'clean', 'bower','build-css','build-template-cache', 'jshint', 'build-js'], function() { return gulp.src('index.html') .pipe(cachebust.references()) .pipe(gulp.dest('dist')); });
Running tests with Karma
One of the de-facto Angular test tool is the Karma test runner, and one the most used testing frameworks with it is Jasmine. The following Gulp task allows to run Jasmine tests from the command line against a headless PhantomJs browser:
gulp.task('test', ['build-js'], function() { var testFiles = [ './test/unit/*.js' ]; return gulp.src(testFiles) .pipe(karma({ configFile: 'karma.conf.js', action: 'run' })) .on('error', function(err) { console.log('karma tests failed: ' + err); throw err; }); });
The following command will launch all the tests in the test suite:
gulp test
Code quality with JsHint
JsHint is one of the most used Javascript linters. The following gulp task allows to integrate it in our build cycle:
gulp.task('jshint', function() { gulp.src('/js/*.js') .pipe(jshint()) .pipe(jshint.reporter('default')); });
A development web server
After doing the main build and running all the tests using the gulp
default task, its useful to be able to startup a local development server.
The gulp-webserver
plugin was configured to do exactly that in the following way:
gulp.task('webserver', ['watch','build'], function() { gulp.src('.') .pipe(webserver({ livereload: false, directoryListing: true, open: "http://localhost:8000/dist/index.html" })); });
Conclusions
With the Gulp task runner and its rich ecosystem of plugins, its possible to create a baseline Angular build with the most frequently used features that an Angular project will likelly need, and still keep the complexity of the build relativelly low.
Let us know your thoughts on the comments bellow, what does your Angular build look like?
Reference: | What every Angular project likely needs – and a Gulp build to provide it from our WCG partner Aleksey Novik at the The JHades Blog blog. |