So far you’ve learned how to do things like compiling code, autoprefixing, cleaning, compressing and minifying all just by typing a few words. This is great, but what if you have a project that would need you to run several of these commands, one after the other, over and over again until you complete your work? For example:
- Compile preprocessor to CSS
- Autoprefix CSS
- Clean CSS
- Compile Jade to HTML
- Concatenate and Minify JavaScript
Even with just a few words per command it would quickly become tiresome throughout the course of a typical site creation process.
This is where “Task Runners” step in to save the day. With task runners you can setup a single file inside your project that defines all the tasks you need to run on your project, and the order they need to run in. In this file you can then define custom commands you can use to execute all those tasks once.
You’ll be learning how to setup task runners in this way through this tutorial, and in the process you’ll also see an example of bringing in scripts from Bower packages for efficient deployment in your projects.
Note: This tutorial assumes you’ve completed all the previous tutorials in this series. If you haven’t yet done that, you’ll find it helpful to go through them before you start here.
The “Big Two” Task Runners
There are actually several task runners available right now, however for the purposes of this tutorial we’ll be focusing on the two which are currently most popular: Grunt and Gulp.
There are several technical differences between the two projects, but for the purposes of simplification, Gulp is easier to use for beginners while Grunt has more customization for better control of the task. I recommend following the steps for using both below then deciding for yourself which one you prefer.
Create an Example Project
We’re going to be creating a project that watches and automatically compiles Stylus and Jade, and optimizes CSS and JavaScript. We’ll achieve this first using Grunt, and then using Gulp.
To begin with, we’ll need to setup an example project with some files inside it that our task runner can operate on. Create a folder named “Grunt Project”, then add a subfolder named “build” and a subfolder named “source”.
In the “source” folder add two new subfolders named “stylus”, “jade”. Add a few example files of the appropriate type to each folder.
The files can contain any code you want, just so you have something you can see the compilation process working on.
Tip: If you’re not sure what code to add, try grabbing some sample code from Codepen: pens tagged stylus, pens tagged jade.
Your directory should look like this:
We’re then also going to take advantage of what we learned about Bower in a previous lesson and grab downloads of jQuery and Modernizr, which we’ll combine and minify later.
Run the commands:
bower install jquery --save
bower install modernizr --save
Now, make a duplicate of your entire project folder and rename it “Gulp Project”.
This way you can follow the steps on using Grunt inside your “Grunt Project” folder, and the steps for using Gulp inside your “Gulp Project” folder.
Getting Started with Grunt
Install the Grunt CLI
In order for Grunt commands to work you’ll need to install its CLI (command line interface). Install it globally with:
[sudo] npm install -g grunt-cli
Setup Project for Grunt
Add package.json file
Every project that uses Grunt will need a “package.json” file in the root folder.
We covered setting up a “package.json” file by using the command npm init
in the previous Installing Packages tutorial. If you haven’t completed that section yet please go back and follow it now.
Install Grunt package
Install Grunt into your project and save it as a development dependency with:
npm install grunt --save-dev
Install Grunt Plugins
You’ll remember that when you wanted to use packages with npm or Bower, you had to search in the right place to get the versions designed to work with each system.
The same thing goes when using packages with Grunt. Through Grunt you can access an ecosystem of plugins, which are essentially wrappers around vanilla npm packages. These plugins are still delivered via npm, but they’re specially equipped to work with Grunt
For example, instead of the npm package UglifyJS, with Grunt you might use the plugin “grunt-contrib-uglify”.
You can search for Grunt plugins at http://gruntjs.com/plugins
For our project we’ll be installing these six Grunt plugins:
- https://www.npmjs.com/package/grunt-contrib-stylus
- https://www.npmjs.com/package/grunt-autoprefixer
- https://www.npmjs.com/package/grunt-contrib-cssmin
- https://www.npmjs.com/package/grunt-contrib-jade
- https://www.npmjs.com/package/grunt-contrib-uglify
- https://www.npmjs.com/package/grunt-contrib-watch
Each one will be installed into your project folder’s “node_modules” subfolder, and saved as a development dependency.
Run each of these commands, one at a time, with your terminal pointed at your “Grunt Project” folder:
npm install grunt-contrib-stylus --save-dev
npm install grunt-autoprefixer --save-dev
npm install grunt-contrib-cssmin --save-dev
npm install grunt-contrib-jade --save-dev
npm install grunt-contrib-uglify --save-dev
npm install grunt-contrib-watch --save-dev
When you’re done, you should see these folders inside your project’s “node_modules” folder:
Add Gruntfile
Every Grunt project also needs to have what’s called a Gruntfile in the root folder.
A Gruntfile is a file named “Gruntfile.js”, or “Gruntfile.coffee” if you prefer writing in CoffeeScript. In our case we’ll be working with JavaScript, so add a file named “Gruntfile.js” to your root folder.
Filling in your Gruntfile will allow you to determine which commands will trigger what tasks to be run. You can start by just adding a basic shell into your Gruntfile. Add the following code to your Gruntfile.js:
module.exports = function(grunt) { };
Enable Plugins via Gruntfile
Now we’re going to use the grunt.loadNpmTasks
method to enable our plugins.
Inside the curly brackets of your existing Gruntfile, we’ll add six lines, one to enable each grunt plugin, like so:
module.exports = function(grunt) { // Load grunt plugins. grunt.loadNpmTasks('grunt-contrib-stylus'); grunt.loadNpmTasks('grunt-autoprefixer'); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-contrib-jade'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-watch'); };
This code registers the name of each plugin as a grunt command, allowing us to use that command to make the plugin run a task. For example, we would use the command grunt stylus
to run a stylus task, grunt autoprefixer
to run an autoprefixer task and so on.
Configure Tasks in Gruntfile
Our grunt plugins are installed and the commands to use each are operational, however if you were to use them right now you wouldn’t see anything happen. The reason is we have to setup some configuration to determine what each task should actually do.
This is done by adding the grunt.initConfig
method to your Gruntfile, and then passing information through it that dictates how you want each task to be run.
First, we’ll add the grunt.initConfig
method above the lines you just added to load grunt plugins:
grunt.initConfig({ });
Now we can go ahead and add the configuration for each of the plugins we installed.
Every plugin has its own range of settings you can use, and these options are detailed on the pages linked to in the “Install Grunt Plugins” section above.
You can also read full detail on configuring Grunt tasks here:http://gruntjs.com/configuring-tasks
Grunt Task Configuration Example: Stylus
We’re going to start by adding configuration for our stylus
task.
In between the curly brackets you just added, on the empty line, add the following code:
stylus: { compile: { options: { compress: false, paths: ['source/stylus'] }, files: { 'build/style.css': 'source/stylus/main.styl' } } }
Your Gruntfile should now look like this:
module.exports = function(grunt) { grunt.initConfig({ stylus: { compile: { options: { compress: false, paths: ['source/stylus'] }, files: { 'build/style.css': 'source/stylus/main.styl' } } }, }); // Load grunt plugins. grunt.loadNpmTasks('grunt-contrib-stylus'); grunt.loadNpmTasks('grunt-autoprefixer'); grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-contrib-jade'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-watch'); };
Let’s go through a breakdown of the code we’ve added here. We won’t break down every task, but looking at this one should give you an idea of the type of syntax used when putting together Grunt task configuration.
As mentioned above, every plugin has different configuration options so when you’re employing a new plugin take a good look at the usage instructions it offers.
The first thing we’ve done is add an entry into our config for our stylus
task with the code:
stylus: { },
Inside that we’ve added a compile
entry to control what happens during compilation:
stylus: { compile: { } },
Inside the compile
task we’ve created a options
area.
We’ve used that area to set the compress
option to false
, because we’ll be doing our code optimization later.
We’ve also set the paths
option to [’source/stylus’]
so if Stylus sees the@import
directive while compiling it will look for files to import in the project’s “source/stylus” folder:
stylus: { compile: { options: { compress: false, paths: ['source/stylus'] } } },
Then after the options
area we’ve added a files
area to control the output directory and file name, as well as the input directory and file name.
We’ve set the output location of our compiled CSS file to be ’build/style.css’
, while the Stylus file to process is ’source/stylus/main.styl’
.
stylus: { compile: { options: { compress: false, paths: ['source/stylus'] }, files: { 'build/style.css': 'source/stylus/main.styl' } } },
Now, with your terminal pointed at your main root folder run the command:
grunt stylus
Look inside your “build” folder and you should see a newly compiled “style.css” file.
Configure the Remaining Tasks
We’ll now move fairly quickly through the configuration of each remaining task. Insert each block of config code immediately after the one you previously added.
Autoprefixer
Add this code:
autoprefixer: { compile: { files: { 'build/style.css': 'build/style.css' }, }, },
Run the autoprefixer task with:
grunt autoprefixer
If you inspect your “build/style.css” file you should now see prefixes added where required.
cssmin
Add this code:
cssmin: { clean: { files: { 'build/style.css': 'build/style.css' } } },
Run the cssmin task with:
grunt cssmin
If you look at your “build/style.css” again now, you’ll see it has been nicely cleaned and compressed for you.
Jade
Add this code:
jade: { compile: { files: [{ expand: true, cwd: "source/jade", src: "*.jade", dest: "build", ext: ".html" }] } },
Run the jade task with:
grunt jade
If you look inside your “build” folder, you should now see an HTML file to correspond with every Jade file you had in your “source/jade” folder.
Uglify
Add this code:
uglify: { bower_js_files: { files: { 'build/output.min.js': [ 'bower_components/jquery/dist/jquery.js', 'bower_components/modernizr/modernizr.js' ] } } },
In this example you’ll see we’re referencing the locations of the Bower components we installed earlier.
We’re grabbing the full expanded versions of both jQuery and Modernizr out of our “bower_components” folder, then concatenating and minifying them into a new file named “output.min.js”. This is a great way to deploy scripts you’re managing with Bower.
Run the uglify task with:
grunt uglify
You should now see a new “output.min.js” file in your “build” folder.
Add a “watch” Task
So far it might seem like we just replaced one command to do a certain task with another command, but what we’ve actually been doing is laying down the groundwork for where Grunt really starts to shine.
The key is Grunt’s ability to have one task run another task. So now we’re going to setup a watch
task that will monitor certain files for changes, then run our stylus
and jade
tasks automatically for us.
Add this code:
watch: { stylus: { files: [ 'source/stylus/*.styl' ], tasks: ['stylus', 'autoprefixer', 'cssmin'] }, jade: { files: [ 'source/jade/*.jade' ], tasks: ['jade'] } },
We’ve first added our watch
task, and then inside that we’ve setup an area forstylus
and for jade
.
The files
option in each sets which files should be watched for changes. Thetasks
option sets which tasks should then be executed when changes happen, and in what order.
For stylus
, we’ve set the watch task to monitor all “.styl” files in the “source/stylus” folder, and when it sees changes it will run the stylus
, autoprefixer
and cssmin
tasks in that order.
So now when the watch task is running, all you have to do is save any of your Stylus files and you’ll automatically get a compiled, autoprefixed and optimized CSS file written into the “build” folder for you.
Likewise for jade
, we’ve set all “.jade” files in the “source/jade” folder to be monitored, and whenever one is saved the jade
task will automatically run and compile the corresponding HTML file in the “build” .
Run the watch task with:
watch: { stylus: { files: [ 'source/stylus/*.styl' ], tasks: ['stylus', 'autoprefixer', 'cssmin'] }, jade: { files: [ 'source/jade/*.jade' ], tasks: ['jade'] } },
Stop it again by either:
- Closing the terminal
- Pressing CTRL + C
Add “default” Task
At this point you might be wondering, what about the JavaScript uglify task?
The reason we didn’t include it with the watch
task is you’re not going to be making changes to the jQuery and Modernizr files the uglify task is processing. So because the watch
task only responds to changes it would never be triggered to process your JavaScript.
Instead, we’re going to make use of the default
task that can be set in your Gruntfile. This is the task that will be run if you use the command grunt
by itself with nothing appended.
After your last grunt.loadNpmTasks
line, but before the closing };
of the file, add this line:
grunt.registerTask('default', ['stylus', 'autoprefixer', 'cssmin', 'jade', 'uglify']);
This sets the default
task to run stylus
, autoprefixer
, cssmin
, jade
and then uglify
.
So now if you run the command grunt
without anything after it, it will build your entire project, including your JavaScript.
Getting started with Gulp
The process of using Gulp is very similar to Grunt so I’ll just speed you through it, noting the differences between the two.
Install Gulp
Install Gulp globally with:
[sudo] npm install gulp -g
Setup Project for Gulp
Add package.json file
As with the Grunt process, add a “package.json” file to your project using the npm init
command.
Install Gulp Package
Install Gulp into your project and save it as a development dependency with:
npm install gulp --save-dev
Install Gulp Plugins
Strictly speaking, Gulp doesn’t actually need to use Gulp plugins because it can actually make use of vanilla npm packages. However, there are several plugins available that are specifically optimized for use with Gulp, and when you’re starting out you’ll find these easier to use.
Search for Gulp plugins at: http://gulpjs.com/plugins/
We’ll be installing these plugins:
- https://www.npmjs.com/package/gulp-stylus
- https://www.npmjs.com/package/gulp-autoprefixer/
- https://www.npmjs.com/package/gulp-minify-css/
- https://www.npmjs.com/package/gulp-jade/
- https://www.npmjs.com/package/gulp-jade/
- https://www.npmjs.com/package/gulp-rename
These plugins perform essentially the same roles as those we used with Grunt, with two differences.
One, we don’t need to install a “watch” plugin as Gulp has one built in.
Two, we’re installing the “gulp-rename” plugin because Gulp only allows you to specify a folder to compile new files into, without being able to specify the names the files should have. We’ll use this plugin to solve that problem by renaming the files we generate on the fly.
Note: we’re using a plugin named “gulp-minify-css” but it employs the same “clean-css” package you’ve used so far.
With your terminal pointed at your “Gulp Project” folder run each of these commands:
npm install gulp-stylus --save-dev
npm install gulp-autoprefixer --save-dev
…you get the idea by now
When you’re done, you should see these folders inside your project’s “node_modules” folder:
Add gulpfile.js
In a parallel to Grunt’s “Gruntfile”, Gulp uses a “Gulpfile”. To the root folder of your “Gulp Project” add a file named “gulpfile.js”.
To get started, we’ll give the file access to the “gulp” package you just installed into your “node_modules” folder, by adding this line to the top of your Gulpfile:
var gulp = require('gulp');
Enable Plugins via Gulpfile
Just as we did with Grunt, we need to enable each of the plugins, this time in our Gulpfile. Instead of Grunt’s method grunt.loadNpmTasks
, we’ll be using the require
function native to NodeJS.
Add these lines to your Gulpfile, below the line you already added.
var stylus = require('gulp-stylus'); var autoprefixer = require('gulp-autoprefixer'); var minifyCSS = require('gulp-minify-css'); var jade = require('gulp-jade'); var uglify = require('gulp-uglify'); var rename = require("gulp-rename");
This approach is different to Grunt in that we don’t yet have any commands registered that can be run at this stage. Rather, we’ve just created JavaScript variables, each representing our plugins, that we can employ later in our Gulpfile.
Configure & Run Tasks in Gulpfile
One of the main differences between Grunt and Gulp is that with Gulp you don’t need to individually configure a task for every plugin you’re using in your project. Instead, you only configure tasks for the actual commands you want to run.
Stylus, Autoprefixer & minifyCSS
In our Gruntfile earlier we setup a separate task each for Stylus, Autoprefixer and clean-css. In our Gulpfile we don’t need to do this. We know that every time we compile our Stylus code we want the resulting CSS to be autoprefixed and minified, so instead we’ll create one single task to do all these things at once.
Add this code to the bottom of your Gulpfile:
gulp.task('css', function () { gulp.src('source/stylus/main.styl') .pipe(stylus({compress: false, paths: ['source/stylus']})) .pipe(autoprefixer()) .pipe(minifyCSS()) .pipe(rename('style.css')) .pipe(gulp.dest('build')) });
Let’s break down what we’ve done.
First, we’re using gulp.task()
to define a new task named css
, and making some space for a JavaScript function that will be run whenever we run the command gulp css.
Next, we’re using gulp.src()
to set the source file we want to process to “source/stylus/main.styl” file.
Then, we start using Gulp’s pipe()
function to call on each of our plugins. The way pipe()
works is like physical pipes, where you feed something into the first pipe and it then passes through every connected pipe.
Our first “pipe” adds Stylus compilation, using the same compress
and paths
options as we did when working with Grunt earlier. We then connect a second pipe, which takes the compiled code and adds autoprefixing, you get the jist.
Now the css
task you just created is ready to go. In your project root folder run:
gulp css
…and your Stylus file will be compiled, autoprefixed and cleaned then output to your “build” folder as “style.css”.
Jade
We’ll use the same process again to setup our task for Jade compilation. We’ll create a task named html
, set it to use all the “.jade” files in the “source/jade” folder as its source, pipe through Jade compilation, then send the resulting HTML file(s) to our “build” folder.
Add this code below the css
task you just created:
<gulp.task('html', function() { gulp.src('source/jade/*.jade') .pipe(jade()) .pipe(gulp.dest('build')) });[/javascript] Run your new task with the command: [shell]gulp html[/shell] <div></div> ...and you’ll see each of your Jade files compiled into corresponding HTML files in your “build” folder. <h4 class="nolinks">Uglify</h4> Now we’re going to use the same approach one more time, setting up a task named <code class="inline">js</code> to take the jQuery and Modernizr files from our “bower_components” folder, uglify (concatenate and minify) them, then output the code as a file named “<span class="skimlinks-unlinked">output.min.js</span>” to our “build” folder. [javascript] gulp.task('js', function() { gulp.src([ 'bower_components/jquery/dist/jquery.js', 'bower_components/modernizr/modernizr.js' ]) .pipe(uglify()) .pipe(rename('output.min.js')) .pipe(gulp.dest('build')) });
Note: in this case we want to specify two source files, so we are passing the two file names as an array, i.e. comma separated values between square brackets.
Run your js
task with the command:
gulp js
…and you’ll see a new file named “output.min.js” appear in your “build” folder, containing jQuery and Modernizr in minified form.
Add a “watch” Task
Now that we have our custom css
and html
tasks setup, we can use Gulp’s in built gulp.watch()
function so they’ll automatically run for us. Add this code to the bottom of your Gulpfile to create a watch
task:
gulp.task('watch', function () { gulp.watch('source/stylus/*.styl', ['css']); gulp.watch('source/jade/*.jade', ['html']); });
The first use of gulp.watch()
sets the css
task to be run whenever a “.styl” file inside the “source/stylus” folder is changed.
The second use of gulp.watch()
sets the html
task to be run whenever a “.jade” file inside the “source/jade” folder is changed.
Run your watch
task with the command
gulp watch
…and whenever you save changes to one of your Stylus or Jade files your compilation will be handled automatically.
Add “default” Task
Just as we did with our Grunt project, we’ll wrap up by creating a default task that will run whenever we use the command gulp
by itself. Add this line to the bottom of your Gulpfile:
gulp.task('default', ['css', 'html', 'js']);
We’re using this task to build our whole project, including the JavaScript, by having it run the css
, html
and js
tasks. To build your entire project with the default task use the magic word:
gulp
In the Next Tut…
…nope. That’s all! And there you have it. Just laying a little bit of groundwork and then all you need is one word of code to build the entire project. Now you know how to install package managers, install plugins via them, use the plugins to make your life easy and then get more efficient (lazy 😉 ) by automating that process as well.
There’s a lot more you can do with these “command line toys” but I aimed to provide a beginner crash course at using command line package managers to make the redundant task of preparing your project for deployment a breeze.
Hope you liked it. Have a good one.