Structuring Your Gulp Workflow

Gulp is a fantastic tool for automating your workflow. From compiling CSS and Javascript, to managing your entire build process, the value it adds to improving your workflow is priceless. Don’t let managing an enormous Gulpfile bog you down.

There are a lot of great articles out there on how to use Gulp to improve your workflow, but something that one seldom comes across is how to effectively manage your workflow.

In this article, I’ll build a small workflow that will watch .scss files, compile Sass to CSS, and inject changes into your page using BrowserSync, while focusing on a clean and maintainable structure.

BrowserSync is similar to LiveReload, but on steroids. If you don’t yet know what BrowserSync can do to improve your workflow, take a gander at Tuts+’s Intro To BrowserSync.

This article assumes you are familiar with installing and requiring npm packages.

Gulp Folder Structure

Many Gulp workflows are managed via a single Gulpfile. Gulpfiles can quickly become unwieldy.

Large files are a nightmare when writing any sort of code. Splitting concerns to their own files makes navigating and maintaining projects easier.

The following is the go-to structure we’ve found excellent for managing Gulp tasks at Fixate, and will form the basis for our workflow here:

```
├── [project src / built files / other configs etc.]
├── ...
├── gulp
│   ├── tasks
│   │   ├── browserSync.js
│   │   ├── css.js
│   │   └── watch.js
│   └── gulpconfig.js
├── Gulpfile.js
├── ...
└── package.json
```

What we’ve got here can be outlined as follows:

  • our Gulpfile sits in the root of our project

  • gulp/ folder contains our gulp-specific files

  • inside this folder we keep a config that our tasks will share to keep our settings DRY

  • we also store our tasks in their very own tasks/ folder, too

  • each category of task gets its own file, properly separating concerns

With this structure in place, we’re ready to get down to building workflow zen.

The Gulpfile

const gulp        = require('gulp');
const requireDir  = require('require-dir');
const browserSync = require('browser-sync').create();

global.browserSync = browserSync;

requireDir('./gulp/tasks', {recurse: false});

gulp.task('default', ['watch']);

This Gulpfile is responsible for creating a global instance of BrowserSync that we can access in tasks, requiring all the files in the gulp/tasks/ folder, and creating the default gulp task, which will fire up our currently non-existent watch task. If you’re not keen on implicitly loading all the tasks with require-dir, you can always explicitly require them.

Note: I’m not a fan of anything implicit in code, nor global properties, but since this isn’t the actual production code, we let it slide in favour of being pragmatic.

And that’s it! Short and sweet, and we know exactly what our Gulpfile is responsible for.

The Config

Before we get into our tasks, it’s important that repeated configurations and options are managed in their own file to make tasks manageable, and eliminate hard-coding the same values in multiple places.

Most gulp tasks expect a src – a file or list of files on which to operate, and a dest – a location to which to output the result of the task.

Let’s add these to our gulpconfig.js:

var src = 'src';
var dest = 'built';

module.exports = {
  path: {
    src: {
      base: src,
      scss: src + '/scss',
    },
    dest: {
      base: dest,
      css: dest + '/css',
    },
  },
};

What we’re implying from this config is that we have src/scss/ directory where our .scss files can be found, and a destination at built/css/ – likely where we want our css to be built and served from.

Any configs can be added to this object, and accessed by our tasks.


The Watch Task

In our Gulpfile we added the default Gulp task. It has one dependency: run the watch task.

Now that we have our config ready, let’s take a look at our watch task:

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

const conf = require('../gulpconfig');

gulp.task('watch', ['css', 'browser-sync'], function() {
  gulp.watch(conf.path.src.scss + '/**/*.scss', ['css:watch']);
});

We require gulpgulp-watch, and our gulpconfig.

We then create the actual watch task, which first starts BrowserSync, and then watches for changes to .scss files at the path we defined in our config. When a .scss file is changed, a css:watch task will be run, which we are yet to create.

Why css:watch? We’re going to be splitting our CSS tasks into their own concerns, too.

Gulp is great for creating tasks that perform only one job, and we’re going to lead with that in mind.


The CSS Tasks

So far we’ve got our watch task that will watch for changes to CSS files, and then run a css:watch task.

Now we can put together our CSS tasks to build our CSS for us.

const gulp         = require('gulp');
const autoprefixer = require('gulp-autoprefixer');
const sass         = require('gulp-sass');

const conf = require('../gulpconfig');

gulp.task('css', function() {
  return gulp.src([conf.path.src.scss + '/**/*.{scss,sass}'])
    .pipe(sass().on('error', sass.logError))
    .pipe(autoprefixer({
      browsers: ['last 2 versions']
    }))
    .pipe(gulp.dest(conf.path.dest.css));
});

gulp.task('css:watch', ['css'], function() {
  return global.browserSync.reload('*.css');
});

We now have two tasks.

The first, css task, is dedicated to compiling .scss files from our src, automatically adding our prefixes using the excellent Autoprefixer, and writing files out to our destination.

The second, css:watch task, is responsible for running our first task, and then following up by telling BrowserSync to reload any CSS files. The reason we can tell BrowserSync to reload is because we have access to the global object we defined in our Gulpfile.

Splitting our tasks like this ensures that our tasks are responsible for as little as possible, making them easy to reuse, and easy to understand.

We’re almost done! The only thing missing is our BrowserSync task.

BrowserSync

Our watch task is elegantly linked to our CSS compilation and BrowserSync reloading.

As in the css:watch task, we’re going to leverage the global instance of BrowserSync, instantiated in our Gulpfile.

Remember, too, that the browser-sync task is a dependency on our watch task – BrowserSync starts up before any watching even begins.

Let’s get our BrowserSync task up and running.

const gulp = require('gulp');

const conf = require('../gulpconfig');

gulp.task('browser-sync', function() {
  return global.browserSync.init({
    server: {
      baseDir: conf.path.src.base,
    },
    injectchanges: true,
  });
});

Running the browser-sync task initialises our BrowserSync instance for the first time, allowing us to notify it when it should reload from other tasks. This is distinct from instantiating it – the BrowserSync instance doesn’t do anything until we explicitly initialise it with browserSync.init().

In BrowserSync’s options, we tell it to serve a site from our built/ folder, defined in our config. If we had an index.html linking our compiled CSS, we would be all set to begin working efficiently on our new project. 

Running Gulp

With everything set up, the last thing to do is to run gulp, and get to work!

$ gulp

In a few seconds we’ll have a server running at built/, watching for changes to our .scss files, and live injecting those changes into our site. 

Wrapping Up

Creating a manageable automated workflow is an easy task with Gulp. Not only can we improve the maintainability by refactoring our folder structure, but we can also think about tasks in a focused way, making it easy to reason about how our tasks work together and on their own, and making adding new tasks effortless.

Contact Larry

Previous
Previous

Make Your CSS Variable Names Suck Less

Next
Next

CSS Display Properties: The Pillars Of Layout