Warp speed frontend development with gulp

Gulp

Objectives

I want to:

  • Automate the process of minifying our CSS
  • Automate the concatenation of javascript files into a single script
  • Be able to compile my Sass/Less automatically
  • Have all assets available in a distribution folder for production use
  • Have the browser refresh whenever we make changes in my CSS and JS files

(Examples of gulpfiles can be found in the “further reading” section.)

Prerequisites

  • A Linux distribution

What is gulp?

Gulp is a task automation utility written in javascript. It is but one of a great many set of utilities that, when put together and executed through the Node.js framework, allow us to automate a lot of tasks.

Node.js

The great thing about relying on Node is that we can use a whole bunch of utilities without having to deal with different versions and dependencies, because node’s npm utility is a package manager.

You should be able to install node using your preferred Linux package manager, which will give you access to the npm utility.

The npm utility allows us to download and stash away pieces of javascript software neatly organized for us to use in our various projects. Developers usually package all the javascript code that they need on a project basis, but those utilities can be installed globally and accessed as with any typical Linux utility (via npm).

package.json

npm looks for project specific information in a package.json file at the root of the current directory. When you run a simple npm install without specifying any package to install, npm will parse the package.json file to see if any packages need to be installed.

This interactive guide will help you navigate the different parameters you can control through a package.json file.

The command npm init will guide you through the creation of a basic package.json file.

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (test)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /mnt/hgfs/www/test/package.json:

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
Is this ok? (yes)

Installing gulp

$ npm install --global gulp

will install gulp globally. Typically, those files will be copied into a node_modules folder inside /usr/lib

Saving dependencies

$ npm install --save-dev gulp

will install gulp in our current working directory, assuming that’s where our website lives. The --save-dev switch takes the extra step of keeping track of all the installed npm utilities so other developers can replicate a given development environment without having to know which packages to install.

If you take a look at the package.json file after install, it should have added gulp as a dependency.

Editing the gulpfile

Now that gulp is installed, it needs some instructions as to what tasks to automate. It looks for such instructions in a gulpfile.js file.

$ vi gulpfile.js

Let’s start with the following code:

var gulp = require('gulp');

gulp.task('default', function() {

});

gulp will always look for a task called default which it will run if you execute gulp without parameters.

As we mentioned before, gulp allows us to automate tasks, but those tasks rely on other utilities:

  • gulp-plumber: gives us a default error handler for when things go wrong. This utility allows the process to report errors without having to stop.
  • gulp-watch: allows us to listen for changes on the file system.
  • gulp-livereload: refreshes the browser when changes are made to dev files.
  • gulp-minify-css: removes spaces and line breaks from CSS files (minify)
  • gulp-uglify: minifies javascript files
  • gulp-rename: allows us to rename files
  • gulp-include: makes it easier to compile our javascript by specifying a list of files to process
  • gulp-sass: compiles scss files
  • gulp-concat: joins files
  • del: deletes files and folders

Quite a handful, isn’t it? The beauty of having a package manager is that you don’t have to know the intricacies of how these packages work, you can just install and use them. Let’s do that in one go:

$ npm install --save-dev gulp gulp-plumber gulp-watch gulp-livereload gulp-minify-css gulp-uglify gulp-rename gulp-include gulp-sass gulp-concat del

npm creates quite an intricate folder structure and relies on symbolic links which can be a problem on windows systems. The --no-bin-links switch will install packages without relying on unix type links.

Error Handling

Let’s go back to our gulpfile and add some lines to it:

var onError = function( err ) {
  console.log( 'An error occurred:', err.message );
  this.emit( 'end' );
}

This will allow gulp-plumber to handle errors properly.

CSS processing

Next, let’s create a task:

gulp.task( 'scss', function() {
    return gulp.src( './sass/style.scss' )
        .pipe( plumber( { errorHandler: onError } ) )
        .pipe( sass() )
        .pipe( minifycss() )
        .pipe( rename( { suffix: '.min' } ) )
        .pipe( gulp.dest( '.' ) )
        .pipe( livereload() );
} );

Gulp chains commands through the pipe method. We specify a working directory, ask it to handle any errors should they arise, compile our sass, minify the resulting CSS file, rename it using the .min.css suffix, place it the current folder and instruct the livereload utility to refresh assets.

Once our gulpfile is saved, we can execute gulp sass to run that specific task.

Javascript processing

Let’s move on to javascript processing:

gulp.task( 'js', function() {
    return gulp.src( './js/manifest.js' )
        .pipe( include() )
        .pipe( rename( { basename: 'main' } ) )
        .pipe( gulp.dest( './js/dist' ) )
        .pipe( uglify() )
        .pipe( rename( { suffix: '.min' } ) )
        .pipe( gulp.dest( './js/dist' ) )
        .pipe( livereload() );
} );

The js task will include all the files we specified in a manifest.js file, will name it main.js, copy it to a dist folder, minify it, renamed the minified file using .min.js as a suffix, send the minified file to the dist folder and refresh the browser.

File system watcher

Finally, let’s instruct gulp to watch the file system for changes:

gulp.task( 'watch', function() {
    livereload.listen();
    gulp.watch( './sass/**/*.scss', [ 'scss' ] );
    gulp.watch( './js/**/*.js', [ 'js' ] );

    gulp.watch( './**/*.php' ).on( 'change', function( file ) {
        livereload.changed( file );
    } );
} );

We instruct gulp to listen for changes in the sass and js folder, the parameter in brackets being the task list to run. We do the same for php files, where modifications will trigger a page reload.

Live Reload

In order for the livereload task to work, we need some way to relay changes to active pages for the current project on our browser. The gulp-reload utility ships with a mini web server that listens on port 35729 by default.

There are two choices when it comes to triggering the livereload task, either you embed a script on your page or you rely on a browser addon, provided one exists for your favourite browser.

If you go for the embedded script, you can add the following code snippet at the bottom of your page:

<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')

For the browser addon option, this page provides more information.

Conclusion

The awesome part of this is that we haven’t even scratched the surface of what npm based utilities can do in terms of workflow automation. The downside of integrating those utilities in our workflow is that they’re still quite unstable due to the relative youth of the community. Of course, that means it’ll get much better as the community grows and tools mature, which in turn will make us more productive!

Further reading