Skip to main content

December 03, 2014

Automated Theming Workflow with Gulp

Thought by FFW,

As humans, we have always tried to develop ways get more work done in a shorter amount of time and reduce man hours. This desire has emerged more than ever in the constantly changing world of technology. Programmers are always looking for ways to rid themselves of the monotony of routine and be more efficient. If you’re one of those programmers, consider streaming the building system Gulp.js - it can do a lot of work for you, such as compilation, minification and more, leaving you with room for creativity! It builds on Node.js and works faster than other systems like Grunt.js and has a simpler configuration. So, why not? Let's make these machines work for us.

Why Gulp?

There are few other tools around for building project processes. Grunt.js is the  most popular one. Gulp is often prefered to use as linear building system, but with Grunt you can make more complex tasks with some logic built in, but you will pay the price with a more complicated configuration and a longer task run time. Gulp works around streams and can be explained in 3 words: source, action and destination. Its simple API has only 5 methods and it is easy to start writing your own configured tasks after reading the get started article. The most useful built-in method is what Grunt presented as a plugin with a simple configuration. In Gulp you set what files you want to watch and what to do when one of them changes. Grunt is harder for usage and making config files. To write a task for that takes much more time, API knowledge and lines of written code. That is one of the many reasons I highly recommend using Gulp.js as build system for front-end projects like Drupal theme.

Installation

Here I’ll show the Gulp integration Drupal theme with IE, later than version 8. However, you can use it with any Drupal theme that you want.  Just change a few of the configuration paths in your gulpfile.js and you can also modify other settings of tasks according to your own project. So let’s start.

  1. You must install Node.js. It has a simple installation for all OS’s that you can find on nodejs.org.
  2. Next open the Node.js command prompt and globally install Gulp.js with the next command:
    npm install -g gulp
  3. Then create the package.json(here we put our gulp project dependencies) and gulpfile.js, that I have already created, in your theme directory or you can use the gulpfile template from Gulp official web-site and write your own tasks.
  4. And to finish installation you must install all Node.js modules locally in your project, just run them from your project folder:
    npm install

Tasks

All tasks in Gulp can be separated in few parts: define task, define source files, define action with source files, define destination files. As you can see all tasks stream through your files and then change. To run a task write gulp ‘name of task’ or only gulp for default task in Node.js prompt. Let’s define tasks for each workflow process. We’ll use the following conribute gulp plugins(if you want to see all available configuration options you can visit their pages on npmjs.org - the repository of all Node.js packges, here you can find a lot of other plugins useful):

  1. gulp-rename - rename files.
  2. gulp-size - show the file size.
  3. gulp-plumber - prevents task breaking on errors.

Styles

Here we’ll define compilation scss files to css, auto prefix them, sort and minify. If you use less you can use gulp-less plugins instead sass.

  1. gulp-ruby-sass - compiles sass to css, compass mixins can be used.
  2. gulp-autoprefixer - add all missing browser prefixes using Can I Use database.
  3. gulp-csscomb - sort, change code style of css rules, you can use your own configuration for sorting according to your own code style.
  4. gulp-minify-css - minify css.
  5. gulp-bless - split css files for IE < 10, to prevent old IE’s bug based on css selectors limit.
  6. gulp-group-css-media-queries - group css rules in media queries that is very useful if you like to write nested media-queries in css preprocessors.

After its completion all our sass code will compile into css, then it will gather all css rules to matching media-queries, set browser vendor prefixes to css rules according to browsers supported in your configuration and Can I Use base. The code will also be minified, split into separate files if it exceeded css selectors limit, will add a min prefix and save to the destination folder.

As you might remember, not so long ago was the post-processing tool - Pleeease and its gulp realisation. It can replace half of the plugins used for this task, but it is a subject for separate post.

Scripts

Here we define a few actions with JavaScript files.

  1. gulp-jshint - validate and detects js errors.
  2. gulp-concat - concatenate js files.
  3. gulp-uglify - minify js files.

After its completion your js files will be validated, concatenated to one file, a min prefix will be added and it will be saved to your destination folder.

Images

Minify and optimize all images for production usage and save them in a minified folder.

  1. gulp-imagemin - minify jpg, png, gif and svg files, reduce their size.

Drush

Clear theme caches with drush command cache-clear theme-registry.

  1. gulp-shell - run commands in shell, you can run any shell commands defined in your system, not only drush.

Watch

Native Gulp file watcher to run tasks after changes in files. When we change scss or sass files it’ll run ‘styles’ task, if we change js files - ‘scripts’ task, any image file - ‘images’, php, inc or info files - ‘drush’.

Default

In any gulpfile you must define the default task it should run with a simple gulp command in the shell. Our default task will run styles, scripts and images tasks.

Result Files

package.json
{

"name""drupal_theme_gulp",
"version" "0.0.1",
"devDependencies" {

"gulp" "^3.8.8",
"gulp-autoprefixer" "^1.0.1",
"gulp-bless" "^3.0.1",
"gulp-concat" "^2.4.1",
"gulp-csscomb" "^3.0.3",
"gulp-group-css-media-queries" "^1.0.0",
"gulp-imagemin" "^1.1.0",
"gulp-jshint" "^1.8.5",
"gulp-minify-css" "^0.3.11",
"gulp-plumber" "^0.6.6",
"gulp-rename" "^1.2.0",
"gulp-ruby-sass" "^0.7.1",
"gulp-shell" "^0.2.10",
"gulp-size" "^1.1.0",
"gulp-uglify" "^1.0.1",
"imagemin-pngcrush" "^2.0.0",
"jshint-stylish" "^1.0.0"
}
}

gulpfile.js

'use strict';

var gulp =require('gulp'),

sass =require('gulp-ruby-sass'),
autoprefixer =require('gulp-autoprefixer'),
csscomb =require('gulp-csscomb'),
minifyCSS =require('gulp-minify-css'),
rename =require('gulp-rename'),
bless =require('gulp-bless'),
gcmq =require('gulp-group-css-media-queries'),
plumber =require('gulp-plumber'),
jshint =require('gulp-jshint'),
concat =require('gulp-concat'),
uglify =require('gulp-uglify'),
imagemin =require('gulp-imagemin'),
pngcrush =require('imagemin-pngcrush'),
size =require('gulp-size'),
shell =require('gulp-shell');

var config = {

sass:'./sass/**/*.{scss,sass}',
css:'./css',
js:'./js',
images:'./images/*.{png,gif,jpeg,jpg,svg}',
imagesmin:'./images/minified',
};

var AUTOPREFIXER_BROWSERS = [

'> 1%',
'ie >= 8',
'ie_mob >= 10',
'ff >= 30',
'chrome >= 34',
'safari >= 7',
'opera >= 23',
'ios >= 7',
'android >= 4',
'bb >= 10'
];

var onError =function(err) {

console.log(err.toString());
this.emit('end');
};

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

return gulp.src(config.sass)

.pipe(plumber({

errorHandler: onError

}))
.pipe(sass({

style:'expanded',
compass:true,
precision:10

}))
.pipe(gcmq())
.pipe(autoprefixer({

browsers: AUTOPREFIXER_BROWSERS,
cascade:false

}))
.pipe(csscomb())
.pipe(gulp.dest(config.css))
.pipe(minifyCSS())
.pipe(rename({suffix:".min"}))
.pipe(bless())
.pipe(gulp.dest(config.css))
.pipe(size({title:'css'}));

});

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

return gulp.src(config.js +'/**/*.js')

.pipe(plumber({

errorHandler: onError

}))
.pipe(jshint())
.pipe(jshint.reporter('jshint-stylish'))
.pipe(concat('main.js'))
.pipe(gulp.dest(config.js))
.pipe(rename({suffix:".min"}))
.pipe(uglify())
.pipe(gulp.dest(config.js));
.pipe(size({title:'js'}));

});

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

return gulp.src(config.images)

.pipe(plumber({

errorHandler: onError

}))
.pipe(imagemin({

optimizationLevel:7,
progressive:true,
interlaced:true,

}))
.pipe(gulp.dest(config.imagesmin))
.pipe(size({title:'images'}));

});

gulp.task('drush', shell.task([

'drush cache-clear theme-registry'

]));

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

gulp.start('styles','scripts','images');

});

gulp.task('watch',[],function() {

gulp.watch(config.js,['scripts']);
gulp.watch(config.sass,['styles']);
gulp.watch(config.images,['images']);
gulp.watch('**/*.{php,inc,info}',['drush']);

});

Benefits

Now we have a few tasks that can be used in your workflow more than once, it’s also very useful if you are working on a team. You can share your gulpfile.js and package.json in your repository and all members of your team will have the same preconfigured project building tool across all operating systems. But don’t forget to add a ‘node_modules’ folder to your gitignore file. In addition you can simply change configuration and extend this project builder according to your current project. For example if you write js code in coffee-script you may add the gulp-coffee plugin to compile coffee files to js. If you don’t want to understand the intricacies of writing gulpfile you can use the Gulp Fiction online tool. With this tool you can simply make tasks using contribute gulp plugins and friendly user interfaces. If you use JetBrains PhpStorm or WebStorm there is a Gulp support in the latest versions, so you can easily run gulp tasks directly from IDE. Hopefully we’ve introduced you to a few steps that will help save you hours of programming over time.