Bower manages the versions of packages we have installed, and the dependencies of each package we wish to use. But how do we go about using those packages in our code efficiently — both for development and deployment?
Gulp vs Grunt
There are two task runners that have gained popularity in this space over the last year, Gulp, and Grunt.
Grunt was the first to gain popularity, and tries to provide built-in functionality to cover the common use cases. It follows a configuration-based approach.
Gulp on the other hand provides very little out of the box, instead preferring to defer functionality to many small single-feature plugins. Gulp uses a streaming pipeline of plugins to create a complex workflow.
While both tools can run tasks in parallel, Gulp does so by default, attempting to achieve multiple concurrency — running as many tasks as possible at the same time while respecting things such as dependent tasks.
Gulp does four things out of the box:
- Define tasks with
- Watch the file system for changes with
- Open files/directories with
- Output files/directories with
Gulp will call the
default task, or any other task specified on the command line, automatically.
Everything else is achieved by calling consecutive
pipe() calls on the result of
Virtual File System & Streaming
Gulp works on a virtual file system, known as vinylfs, which sits on top of the vinyl virtual file format. This means you can modify files without touching the disk until you are finished — allowing gulp to do its multi-pipe streaming without having to write to temporary files.
To learn more about streaming, read the Stream Handbook.
Gulp installation is identical to Bower, to install globally:
To install locally, and save to our
Creating our Workflow
Let's say we want create a single site-wide CSS and site-wide JS file that will automatically be included in our template. We also need the ability to easily switch to the original files for debugging.
Our workflow has two goals. Let's look at the first, minify/concatenation:
- Find all the files being used
- Minify them
- Concat them
- Save them
- Replace the references in our templates
To do this, we will use the
gulp-usemin packages. To install them simply do:
Our workflow then might look something like this:
For this, let's assume that our template currently resides in
/src/templates/layout.tpl. First, we will copy this to:
/src/templates/layout.src.tpl. This file contains the information for gulp to work on, generating our
layout.tpl for production or development, as appropriate.
Next, let's add some directives to our template for
Next, let's build out our tasks. We do this in
gulpfile.js. First, we need to pull in all of our required modules:
Next, we define our
Stepping through this line by line, we define our task with
default, and with a callback function.
We then open our
pipe() it to
usemin, with a configuration that specifies the location of the assets used in our templates (
uglify() and passing in the
gulp.dest() to save all the files.
To run this, simply call
gulp on the command line:
However, we have two issues. The template is copied to
app/templates/layout.tpl, and we are missing the bootstrap
To fix this, let's build some tasks, first a
fix-template task, which will use the
gulp-rename will rename the file opened by
gulp.src in the virtual vinylfs while
gulp-rimraf will remove the original file from disk. We then output the in-memory file to its new location.
To make this automatically run, we could specify this task as a dependency for the
default task, but that means it would run first, before the file is put in the wrong place, so instead, we have to do it the other way around.
First, let's rename our default task to
minify, by changing:
Then, add the minify task as dependency of the
fix-template task, by specifying it as the second argument for
We then run our tasks in the correct order by running gulp with:
This still doesn't work as we'd expect however! Because our
minify task is set to run asynchronously (the default is maximum concurrency), the dependency just requires that it be called, not that it be completed.
We can fix this in three ways,
returning a valid stream, using a callback, or using a promise.
The simplest way is to use a return on the stream: simply prefix the first line of our task with
Seeing as the minify/fix-template is our default case, we can create a new empty
default task with
fix-template as a dependency and it will automatically run:
Better yet, we can specify all our tasks here, so that gulp will attempt to run as many as possible:
This also means that should we ever resolve the dependency on
minify will still be called, also any other tasks that depend on minify can run immediately instead of depending on
fix-template to be called.
The last thing we need to do is fix the bootstrap fonts. Currently they still reside in the
public/bower_components/bootstrap/dist/fonts directory, but our
site.css still points to the relative path
We can handle this two ways: we can copy the fonts to our
public directory... or we can just update the file to point to the copy inside of our
bower_components. To do this we use the simple
Notice again how we have a dependency on the
minify task; we should also add the task to our
Now, thanks to concurrency, the
fix-paths tasks will (potentially) both run concurrently after
minify is completed.
One final thing we should add is a header to indicate the file has been auto-generated, and not to modify it directly. This can be achieved, by — you guessed it —
This time we need to depend on
fix-template as the template file must be in its final location.
Development and Cleanup
Let's create two more simple tasks, a
clean task, and a
clean task simply deletes all the generated files, while the
dev task copies
layout.tpl adding our auto-generated header — leaving the original
bower_component paths in place.
We can also add the clean task as a dependency for the
Now when we run
gulp, we will see:
This is now our production deployment option.
If we want to automatically keep our minified files up-to-date during development, we can use the
gulp.watch() functionality. Let's create our final task,
Here we are watching our templates, as well as all Bower package
.css files. Note that we exclude
We then call
gulp.watch() passing in our array of files, and the task we want to run when changes are detected:
At which point
gulp will sit and wait for changes.
If you want to make sure that the task runs on start up, you can set the
default task as a dependency:
Automating the Automation
One thing you may have noticed is the large number of
require calls we need to make to include all of our needed plugins.
We can shorten this to just one, using — ironically — another plugin,
This plugin will automatically load all gulp plugins from our
package.json using lazy-loading, making them all accessible via a single object.
From this point, all our plugins will be available as
$.<plugin>, stripping the
gulp- and using camel-case naming. This means that
$.uglify respectively, and
You can see the completed
gulpfile.js and other related files, in this gist.
Note: This is example code, not intended for production!
Take a Breath
The frontend tool chain is still very much under development. It is definitely standing on the shoulders of giants, like Composer, Bundler, and particularly npm. Hopefully, it will one day become a giant in its own right.
Being node.js tools, they are typically asynchronous, enabling them to perform with maximum concurrency, as fast as possible — which is critical for speedy deployment.
Get Up and Running
Now that we are using gulp and Bower, in the next part of this series, we will look at Yeoman — a tool for automating the scaffolding when you create your next application.