Starting from scratch with Gulp

an introduction to getting your project setup with the Gulp task runner

Posted by Wazoo on December 15, 2018 · 23 mins read

Within the current maelstrom of web development, you’ve probably heard of Gulp, the JavaScript task runner that runs on Node.js. Maybe you’ve tried to get it up and running, but got lost or confused during setup. Perhaps you’ve known about it, but you’re not sure if it’s the right fit for your project in 2018.

I’m going to show you how Gulp can still be a successful addition to your toolbelt today. I’ll show you how to get started, and go from zero to Gulp-ing!

Special Note: This article is using the latest update of Gulp which is version *4.0+*.

Before you start

  • Ability to make a basic website with HTML, CSS, and JavaScript.
  • Using the terminal / console window in Mac / Linux / PC.

Goals

  • Understand enough about Node.js to get Gulp up and running
  • Create a Sass file
  • Utilize Gulp to compile multiple Sass files into one CSS file, apply any prefixes, and minify.
  • Minify your JavaScript.
  • Run one command that will “host” your webpage and watch your entire directory for updates.

What is a “Task Runner”?

Even with the availability of Webpack, Grunt and Gulp are pretty much the two most popular JavaScript task runners. The purpose of a task runner is to automate pieces of your workflow, to make creating and debugging web applications a whole lot more efficient.

What is the difference between Gulp and Grunt?

You might have noticed that I also have a similar Task Runner article using Grunt. You might be asking yourself, What is the difference between the two? Is there one?.

When I think of Gulp, I think of the concepts of pipes or streams. In other words, most of the way Gulp works is to use Node streams within its internals. Try to imagine setting a small toy boat on a gently moving river stream. Imagine how the boat travels down stream adjusting to any small current or debris along the way. A Node stream is somewhat similar; instead of a toy boat, we have a JavaScript or CSS file. As this file goes down the stream, we’re applying different modifications to it.

Grunt operates in a somewhat similar fashion, however its internal mechanism relies on temporary files instead of streams.

In terms of longevity and maintenance, both task runners aren’t really going anywhere however Gulp gets a slight edge in terms of being currently active in 2018+. While Grunt and its plugins are all available, they are on life support. This is by no means a detractor from using or learning Grunt, it’s still a useful and handy addition.

Installation

Before we can go into working with Gulp, we’ve got to get some tooling installed to help us out.

Install Node.js

You can install either the LTS or the current release of Node.js. If you’re on a Mac, you can install Node via Homebrew:

brew install node

Otherwise, grab the download you want to install on your platform of choice. Using all of the defaults is fine during the installation.

Install gulp-cli globally

We can finally install Gulp. Sorta. We’re not actually installing Gulp per-se, but the Gulp command line interface (CLI). This basically just allows your computer to recognize the gulp command at all times. However, without an install of gulp in your current project, you won’t be able to use Gulp.

npm install -g gulp-cli

The best part of this is that you only need to do this once. Node and the Gulp-CLI just need to be available on your system.

From this point on, you don’t need to install or worry about them again.

Create Your Project

Let’s go ahead and create a new project folder to work in. In your terminal window, create a folder:

mkdir gulp-and-sass
cd gulp-and-sass

As you may already know, Node manages the libraries for your application, known as packages, via the help of NPM - literally the Node Package Manager.

Note: Just for fun, visit https://npmjs.com and notice the banner in the top-left corner of the site. Hit refresh a few times to see a different definition of the NPM abbreviation.

It’s not crazy important to go into too many details about NPM right now, but I think it’s helpful to know that NPM will help you use and manage the huge amounts of cool JavaScript packages available for you.

npm init --y

This creates a default package.json file in the current directory. The package.json file is the main configuration file for pretty much everything JavaScript. This command you’re using just creates a starter package.json for you, without bothering you for any details…

{
  "name": "gulp-and-sass",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

We’ll go through the whole package.json file in the future, but for now notice that the name field has been populated automatically with the name of the folder you’re in. The scripts object is going to be important later on, but we’ll leave that for now.

Let’s use NPM to install Gulp!

npm install gulp --save-dev

After some lines scroll by in the terminal console, you should now see a change in the package.json with the addition of a devDependencies object:

  "devDependencies": {
    "gulp": "^4.0.2"
  }

Congratulations! Gulp is now available in your project! It should be available to us to use right away!

Back in the terminal, just type:

gulp

What happened? The Gulp task runner ran, but we should be seeing a message in our console, No gulpfile found.

No problem, let’s fix that right away. In the terminal window, type:

touch gulpfile.js

This will create an empty file for us in the current directory, called (you guessed it!) gulpfile.js.

a basic gulpfile

Open the file in your editor and type in the following:

function defaultTask(cb) {
    // place code for your default task here
    cb();
}

exports.default = defaultTask;

The Gulp task runner uses the gulpfile.js as it’s main configuration file to rely on the tasks you’re trying to create and run via Gulp.

With the newer versions of Gulp, the recommended approach is to write the tasks as functions within the gulpfile.js, then using exports to expose the specific Tasks.

This should hopefully start to make sense as we add more tasks to it.

defining the default Task

We can create a simple function that receives a special gulp parameter – a callback function, which can come in handy when supporting asynchronous Tasks. For now, we don’t need to worry about it; we just need to call the given parameter when we are finished within our Task.

Let’s do this now. Update your Gulpfile.js:

function defaultTask(cb) {
    // place code for your default task here
    console.log('I am the default Task!');
    cb();
}

exports.default = defaultTask;

Notice that we’re exporting the defaultTask via the exports.default.

Now when you type gulp on the command line, you should see…

[22:25:14] Using gulpfile C:\git-root\gulp-and-sass\gulpfile.js
[22:25:14] Starting 'default'...
I am the default task!
[22:25:14] Finished 'default' after 6.1 ms

loading some default Gulp plugins

Part of the real power behind Gulp is its widely available library of plugins available. A Gulp plugin is merely a library or function that has a very specific job to perform. To use an example, one plugin that we’ll be using is the gulp-uglify plugin. The single function of this task plugin is to minify (ie. compress) the JavaScript source files in our web application.

basic web application

To help continue demonstrating how Gulp is useful for your web application, let’s now include a basic HTML template, along with some SCSS and some JavaScript. Not a terribly huge web application by any stretch, but it’s going to help provide some clarity.

Create 3 files: index.html, src/scss/styles.scss and src/js/entry.js.

touch index.html
touch src/scss/styles.scss
touch src/js/entry.js
// src/scss/style.scss
$font-color: #434343;
$red-color: #ff0000;

canvas {
    background-color: $red-color;
}

h1 {
    color: $font-color;
}
// src/js/entry.js
window.onload = function() {
    console.log('app loaded');
    var root = document.getElementById('root');
    var el = document.createElement('canvas');
    el.width = '400';
    el.height = '400';
    root.appendChild(el);
};
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="dist/style.css" rel="stylesheet" />
    </head>
    <body>
        <div id="root"></div>
    </body>
    <script src="dist/bundle.min.js"></script>
</html>

So you might have already guessed, but the only thing this template does, is display a red square.

bringing in gulp-connect

First, let’s add a Gulp plugin to use as a small web server on our machine. As you might imagine this is a common need for a web developer. A popular plugin to handle this in Grunt is gulp-connect.

npm install gulp-connect --save-dev

We just need to store a reference to the gulp-connect plugin with the help of a const. Then we define a small function to use this plugin reference, and we’re done!

To show you what that means, open up the gulpfile.js and add a function for the plugin we just added - gulp-connect.

    const connect = require('gulp-connect');

    function connectTask() {
      return connect.server({
          port: 3000,
          hostname: '*',
          root: '.',
          livereload: true
      });
    }

    exports.connect = connectTask;

The connectTask function is very small, with just a call to the connect.server() method. We’re defining several configuration properties such as port, hostname and root - which is just our current working directory.

Don’t forget to export a reference to connectTask with the help of the exports object.

If we now run gulp connect on the command line, you should see some output about the local web server. You should be able to open http://localhost:3000 in your favorite browser.

gulp-uglify

Now let’s minify the JavaScript source in our tiny web application. Using gulp-uglify should accomplish this. While not a necessity, it’s a best practice to minify our JavaScript when putting your code out on the Internet. The smaller filesize will help any browser coming to your site load things a bit quicker, and also helps reduce the amount of data being delivered from your host to help keep your own costs down.

npm install gulp-uglify --save-dev

As with grunt-contrib-connect, we need to specify a configuration object within Gruntfile.js.

uglify: {
    src: {
      options: {
        sourceMap: true,
        sourceMapName: './dist/bundle.min.map'
      },
      files: {
        './dist/bundle.min.js': ['entry.js']
      }
    }
  }

We’re defining an uglify object with several options defined. A sourceMap is not always required, but it can help when trying to trace down an error in the browser’s JavaScript console. The files object is letting us specify which files to use. We are taking the entry.js file and outputting the result to create bundle.min.js in the dist folder which will be created if it doesn’t exist.

You might start to realize at this point that since we are generating an output for the JavaScript, we need to update the index.html to make sure we reference it.

  <script src="dist/bundle.min.js"></script>

So now to double check run:

You should still see our favorite red square.

adding sass support via grunt-contrib-sass

I promised in our intro that we would be delving into SASS support, let’s do it and pull in the grunt-contrib-sass plugin.

mv style.css style.scss
npm install grunt-contrib-sass node-sass --save-dev

As you noticed, we’ve renamed our current CSS stylesheet to style.scss which is the common extension for SASS support.

// style.scss

$font-color: #434343;
$red-color: #ff0000;

canvas {
    background-color: $red-color;
}

h1 {
    color: $font-color;
}

Just some basic usage of SASS variables to make sure we can’t reference this stylesheet directly.

Now let’s create the sass object definition in the Gruntfile.js:

sass: {
    dist: {
      files: {
        'dist/style.css': 'style.scss'
      }
    }
  },

As with the JavaScript source file reference we had to update in the index.html, we’ll have to do the same thing for our dist/style.css.

  <link href="dist/style.css" rel="stylesheet" />

So now you should be able to run our Grunt tasks:

Minifying the CSS with grunt-contrib-cssmin

Let’s now add the ability to minify our CSS using the grunt-contrib-cssmin plugin. grunt-postcss also comes in handy here to give us the ability to apply autoprefixer to our CSS styles to account for vendor prefixes, etc.

npm install grunt-postcss grunt-contrib-cssmin autoprefixer --save-dev

postcss configuration

I’m going to pull in autoprefixer to add vendor prefixes and minify the CSS.

postcss: { 
  options: {
    map: false,
  },
  dist: {
    src: 'dist/style.css'
  }
},

defining the cssmin object configuration

cssmin: {
  target: {
    files: [{
      expand: true,
      cwd: 'dist',
      src: ['*.css', '!*.min.css'],
      dest: 'dist',
      ext: '.min.css'
}]
  }
},

NOW we can finally run all of our Grunt tasks together:

  • grunt sass
  • grunt postcss
  • grunt cssmin
  • grunt uglify
  • grunt connect
  • opening the page at http://localhost:3000

Hurrah! However we definitely do not want to type that in every time we make changes to our web application; otherwise it defeats the entire purpose of using Grunt!

Let’s change this now.

back to our default grunt task

So in the start of our Grunt section, we specified an empty Grunt default task:

grunt.registerTask('default', []);

You’ll notice the empty Array as the second parameter. This now comes in handy as we can list the Grunt tasks that we want to use when the default Task is used. Let’s create a few Tasks to help us out:

grunt.registerTask('build', [
    'sass',
    'postcss',
    'cssmin',
    'uglify'
]);

grunt.registerTask('serve', [
    'build',
    'connect'
]);

grunt.registerTask('default', [
    'build'
]);

For a bit of flexibility, we’re defining 3 Tasks - build, serve and default:

  • grunt build - this Task is solely for sassifying and minifying our CSS and JS file(s).
  • grunt serve - this Task calls the build Task, then calls the connect task to start our local web server.
  • grunt - the default Task is just to run our build Task

The whole Gruntfile.js is here for your reference and pleasure:

// Load Grunt
module.exports = function(grunt) {

    // require it at the top and pass in the grunt instance
    require('time-grunt')(grunt);

    // Load all grunt tasks matching the ['grunt-*', '@*/grunt-*'] patterns
    require('load-grunt-tasks')(grunt);

    grunt.initConfig({
      pkg: grunt.file.readJSON('package.json'),
      
      connect: {
          server: {
            options: {
                port: 3000,
                hostname: '*',
                base: '.',
                keepalive: true
            }

          }
      },

      cssmin: { 
        target: {
          files: [{
            expand: true,
            cwd: 'dist',
            src: ['*.css', '!*.min.css'],
            dest: 'dist',
            ext: '.min.css'
            }]
        }
      },

      postcss: {
        options: {
          map: false
        },
        dist: {
          src: 'dist/style.css'
        }
      },

      sass: {
        dist: {
          files: {
            'dist/style.css': 'style.scss'
          }
        }
      },

      uglify: {
        dist: {
          options: {
            compress: true,
            mangle: true,
            sourceMap: true,
            sourceMapName: './dist/bundle.min.map'
          },
          files: {
            './dist/bundle.min.js': ['entry.js']
          }
        }
      }
    });

    grunt.registerTask('build', [
        'sass',
        'postcss',
        'cssmin',
        'uglify'
    ]);

    grunt.registerTask('serve', [
        'build',
        'connect'
    ]);
    
    grunt.registerTask('default', [
        'build'
    ]);
};