Instant Zurb Foundation 4: Get up and running in an instant with Zurb Foundation 4 Framework (2013)

6. Optimizing for Production

We already have our application running, but it has several CSS files with parts of Bootstrap that we may never use. Too many JavaScript files, which involve multiple requests to the server and make our page take longer to load:

   [...]
   <link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.css" />
   <link rel="stylesheet" href="lib/angular-motion/dist/angular-motion.min.css" />
   <link rel="stylesheet" href="lib/fontawesome/css/font-awesome.css" />
   [...]
   <script src="lib/jquery/dist/jquery.js"></script>
   <script src="lib/angular/angular.js"></script>
   <script src="lib/bootstrap/dist/js/bootstrap.js"></script>
   <script src="lib/angular-animate/angular-animate.js"></script>
   <script src="lib/angular-strap/dist/angular-strap.min.js"></script>
   <script src="lib/angular-strap/dist/angular-strap.tpl.min.js"></script>
   <script src="lib/angular-route/angular-route.js"></script>
   <script src="lib/angular-resource/angular-resource.js"></script>
   <script src="/scripts/templates.js"></script>
   <script src="/scripts/services.js"></script>
   <script src="/scripts/controllers.js"></script>
   <script src="/scripts/app.js"></script>
   [...]

Ideally, our HTML would only have one CSS file to call, and just one JS file. We should concatenate them into a single file and if possible minify them (removing blank spaces and leaving all the content on one line), so that the file weighs less and loads faster.

Doing it by hand is a rather tedious and unproductive task, which is why we have task automators like Gulp to do it for us.

We are going to install some Gulp plugins that will help us with these tasks, which we do via npm:

$ npm install --save-dev gulp-minify-css
$ npm install --save-dev gulp-angular-templatecache
$ npm install --save-dev gulp-uncss
$ npm install --save-dev gulp-if

Next we will look at what each plugin does and which of the tasks within Gulpfile.js we are going to implement them in.

6.1 Template Caching

The first task that we are going to implement is the caching of HTML templates, as a module of AngularJS, thanks to the plugin gulp-angular-templatecache, by adding the following to our Gulpfile.js:

var templateCache = require('gulp-angular-templatecache');

gulp.task('templates', function() {
  gulp.src('./app/views/**/*.tpl.html')
    .pipe(templateCache({
      root: 'views/',
      module: 'blog.templates',
      standalone: true
    }))
    .pipe(gulp.dest('./app/scripts'));
});

This task creates a templates.js file in the directory app/scripts/ with the content of the HTML templates cached as String to use it as a dependency in the application.

To do this we need to include the new module created in the main file, app/scripts/app.js, which is the one that uses the views:

angular.module('blog', ['ngRoute', 'blog.controllers', 'blog.templates']);

With this we haven’t minified anything, we have just created a new JavaScript file that contains the HTML templates, so we saved more calls.

6.2 Concatenation of JS and CSS Files

Now we begin to concatenate and minify. We are going to do it directly in the HTML and with a Gulp task. We take our index.html and we will add the following comments: <!-- build:css css/style.min.css --> between the links to the CSS files to concatenate and minify them, and <!-- build:js js/app.min.js --> and <!-- build:js js/vendor.min.js --> to do the same but with the JS files.

   [...]
   <!-- build:css css/style.min.css -->
   <!-- bower:css -->
   <link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.css" />
   <link rel="stylesheet" href="lib/angular-motion/dist/angular-motion.min.css" />
   <link rel="stylesheet" href="lib/fontawesome/css/font-awesome.css" />
   <!-- endbower -->
   <!-- inject:css -->
   <link rel="stylesheet" href="/stylesheets/main.css">
   <!-- endinject -->
   <!-- endbuild -->
   [...]
   <!-- build:js js/vendor.min.js -->
   <!-- bower:js -->
   <script src="lib/jquery/dist/jquery.js"></script>
   <script src="lib/angular/angular.js"></script>
   <script src="lib/bootstrap/dist/js/bootstrap.js"></script>
   <script src="lib/angular-animate/angular-animate.js"></script>
   <script src="lib/angular-strap/dist/angular-strap.min.js"></script>
   <script src="lib/angular-strap/dist/angular-strap.tpl.min.js"></script>
   <script src="lib/angular-route/angular-route.js"></script>
   <script src="lib/angular-resource/angular-resource.js"></script>
   <!-- endbower -->
   <!-- endbuild -->
   <!-- build:js js/app.min.js -->
   <!-- inject:js -->
   <script src="/scripts/templates.js"></script>
   <script src="/scripts/services.js"></script>
   <script src="/scripts/controllers.js"></script>
   <script src="/scripts/app.js"></script>
   <!-- endinject -->
   <!-- endbuild -->
   [...]

We accompany this with the following task in Gulpfile.js:

var gulpif    = require('gulp-if');
var minifyCss = require('gulp-minify-css');
var useref    = require('gulp-useref');
var uglify    = require('gulp-uglify');

gulp.task('compress', function() {
  gulp.src('./app/index.html')
    .pipe(useref.assets())
    .pipe(gulpif('*.js', uglify({ mangle: false })))
    .pipe(gulpif('*.css', minifyCss()))
    .pipe(gulp.dest('./dist'));
});

This task will deposit linked files from index.html in the new directory for production that will be /dist, already minified.

We need index.html in this directory too, but without the comments and with the links to the new minified files. This is achieved with the following task:

gulp.task('copy', function() {
  gulp.src('./app/index.html')
    .pipe(useref())
    .pipe(gulp.dest('./dist'));
  gulp.src('./app/lib/fontawesome/fonts/**')
    .pipe(gulp.dest('./dist/fonts'));
});

In addition, this task copies the font files that we use in the library fontawesome.

Now we include all of these tasks within a new task that we’ll call build and that we will run whenever we want to have our code ready for production:

gulp.task('build', ['templates', 'compress', 'copy']);

In the directory /dist we have the following files with this structure:

/dist
  ├── /js
  |    ├── vendor.min.js
  |    └── app.min.js
  ├── /css
  |    └── styles.min.css
  └── index.html

The files within the folders /js and /css being minified files, so that they occupy as little space as possible.

6.3 Production File Server

To test that everything is fine, we are going to create a new task in Gulp that will allow us to serve the files in the directory /dist like we did with the development version:

gulp.task('server-dist', function() {
  connect.server({
    root: './dist',
    hostname: '0.0.0.0',
    port: 8080,
    livereload: true,
    middleware: function(connect, opt) {
      return [ historyApiFallback ];
    }
  });
});

So, to test our application in production version before uploading it to a server, we must run the following in our terminal:

$ gulp build
$ gulp server-dist

And direct our browser to the URL http://localhost:8080. Here we see the same application running itself, but in this case index.html only has a link to one .css file and a couple of links to .js files, which are the library file and the application file. With them all minified, therefore reducing the number of HTTP requests and having less weight, their load time is shorter.

6.4 Reducing CSS Code

If we look closely at the browser’s development tools, specifically the Network tab, we can see the requests that our application makes to the files that it links. We realize that the styles.min.css file occupies 146 kB

It isn’t a lot, but then we consider that this file contains all of the Bootstrap classes and all of the Font Awesome classes and we’re not using all of them. Is there a way to eliminate the classes that we don’t use? Yes there is, with a Gulp plugin called gulp-uncss. This allows us to indicate which CSS file we want to edit and the HTML files that it should focus on in order to remove the unused CSS, and then our very reduced CSS is ready. We install the plugin:

$ npm install --save-dev gulp-uncss

And implement the task:

var uncss = require('gulp-uncss');

gulp.task('uncss', function() {
  gulp.src('./dist/css/style.min.css')
    .pipe(uncss({
      html: ['./app/index.html', './app/views/post-detail.tpl.html', './app/views/post-list.tpl.html']
    }))
    .pipe(gulp.dest('./dist/css'));
});

gulp.task('build', ['templates', 'compress', 'copy', 'uncss']);

We run gulp-build and gulp server-dist, and when we look at the Network tab we have the following: Now styles.min.css only occupies 13.5 kB which allows it to load much faster.

6.5 Complete Gulpfile

Finally, this would be the complete code of the file Gulpfile.js:

// File: Gulpfile.js
'use strict';

var gulp      = require('gulp'),
    connect   = require('gulp-connect'),
    stylus    = require('gulp-stylus'),
    nib       = require('nib'),
    jshint    = require('gulp-jshint'),
    stylish   = require('jshint-stylish'),
    inject    = require('gulp-inject'),
    wiredep   = require('wiredep').stream,
    gulpif    = require('gulp-if'),
    minifyCss = require('gulp-minify-css'),
    useref    = require('gulp-useref'),
    uglify    = require('gulp-uglify'),
    uncss     = require('gulp-uncss'),
    angularFilesort = require('gulp-angular-filesort'),
    templateCache = require('gulp-angular-templatecache'),
    historyApiFallback = require('connect-history-api-fallback');

gulp.task('server', function() {
  connect.server({
    root: './app',
    hostname: '0.0.0.0',
    port: 8080,
    livereload: true,
    middleware: function(connect, opt) {
      return [ historyApiFallback ];
    }
  });
});

gulp.task('server-dist', function() {
  connect.server({
    root: './dist',
    hostname: '0.0.0.0',
    port: 8080,
    livereload: true,
    middleware: function(connect, opt) {
      return [ historyApiFallback ];
    }
  });
});

gulp.task('jshint', function() {
  return gulp.src('./app/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('jshint-stylish'))
    .pipe(jshint.reporter('fail'));
});

gulp.task('css', function() {
  gulp.src('./app/stylesheets/main.styl')
    .pipe(stylus({ use: nib() }))
    .pipe(gulp.dest('./app/stylesheets'))
    .pipe(connect.reload());
});

gulp.task('html', function() {
  gulp.src('./app/**/*.html')
    .pipe()
    .pipe(connect.reload());
});

gulp.task('inject', function() {
  return gulp.src('index.html', {cwd: './app'})
    .pipe(inject(
      gulp.src(['./app/scripts/**/*.js']).pipe(angularFilesort()), {
      read: false,
      ignorePath: '/app'
    }))
    .pipe(inject(
      gulp.src(['./app/stylesheets/**/*.css']), {
        read: false,
        ignorePath: '/app'
      }
    ))
    .pipe(gulp.dest('./app'));
});

gulp.task('wiredep', function () {
  gulp.src('./app/index.html')
    .pipe(wiredep({
      directory: './app/lib'
    }))
    .pipe(gulp.dest('./app'));
});

gulp.task('templates', function() {
  gulp.src('./app/views/**/*.tpl.html')
    .pipe(templateCache({
      root: 'views/',
      module: 'blog.templates',
      standalone: true
    }))
    .pipe(gulp.dest('./app/scripts'));
});

gulp.task('compress', function() {
  gulp.src('./app/index.html')
    .pipe(useref.assets())
    .pipe(gulpif('*.js', uglify({mangle: false })))
    .pipe(gulpif('*.css', minifyCss()))
    .pipe(gulp.dest('./dist'));
});

gulp.task('uncss', function() {
  gulp.src('./dist/css/style.min.css')
    .pipe(uncss({
      html: ['./app/index.html', './app/views/post-list.tpl.html', './app/views/post-detail.tpl.html']
    }))
    .pipe(gulp.dest('./dist/css'));
});

gulp.task('copy', function() {
  gulp.src('./app/index.html')
    .pipe(useref())
    .pipe(gulp.dest('./dist'));
  gulp.src('./app/lib/fontawesome/fonts/**')
    .pipe(gulp.dest('./dist/fonts'));
});

gulp.task('watch', function() {
  gulp.watch(['./app/**/*.html'], ['html', 'templates']);
  gulp.watch(['./app/stylesheets/**/*.styl'], ['css', 'inject']);
  gulp.watch(['./app/scripts/**/*.js', './Gulpfile.js'], ['jshint', 'inject']);
  gulp.watch(['./bower.json'], ['wiredep']);
});

gulp.task('default', ['server', 'templates', 'inject', 'wiredep', 'watch']);
gulp.task('build', ['templates', 'compress', 'copy', 'uncss']);

As you have seen throughout the development of this example web application, we have programmed in an agile way without having to constantly reload the browser in order to see the changes and forgetting to do repetitive tasks by leaving that work to Gulp. We have also used a CSS preprocessor that if our web project had been larger, would have helped us to maintain the design in a modular, maintainable and scalable way. Just like Angular.js, the Model-View-Controller framework for the front end that we have used. Angular is much more and its learning curve has many ups and downs, but I think that what we’ve seen in this example is useful to start developing more complex applications in a scalable way.

I invite you to continue investigating and using Angular in your projects. Every day you will discover a new way to use it. That is what makes it so powerful.

I hope that this book has been useful to you and that you have learned something new. You can contact me on social networks and let me know your thoughts, any feedback is welcomed and appreciated:

Share your opinion on Twitter at #ebookAngular hashtag :)

·               twitter.com/carlosazaustre

·               github.com/carlosazaustre

·               google.com/+CarlosAzaustre

·               facebook.com/carlosazaustre.web

·               linkedin.com/in/carlosazaustre