Tuesday, January 05, 2016

Using Gulp for WordPress Automation

Using Gulp for WordPress Automation

If you are not using Gulp or any task runner, believe me when I say that you are missing out on all the front-end fun stuff. Today, I intend to share my advanced Gulp based automatted workflow for building WordPress themes, with the community.

I remember the time when I had to minify CSS and JS files, compress the images and the fuss of testing my designs across different browsers let alone devices — oh my. If my recollection of lost isn't misleading me then I used to spend like 30% of my productive hours on this stuff.

Thankfully, someone wrote a piece about DRY (Don't Repeat Yourself) and it got me thinking about the flaws in my workflow. I believe a tweet from Chris Coyer led me to start using Grunt in 2012 or a little later. I used it for over a year and boy it was a fun ride.

Why Gulp?

While Grunt took care of optimizing images, compressing scripts, and compiling Sass, I never really enjoyed writing Grunt files. Then in Dec 2013, I heard about this new task runner called Gulp. On a Sunday morning, I thought to create a dummy project and try Gulp, to say that I was hooked would be an understatement.

Why Do I Like Gulp?

At that time, there was hardly any documentation available for using Gulp with WordPress, but thanks to the awesome helping community at Stack Exchange, I was able to make it work. I found Gulp to be a lot better than Grunt. Here is why:

  • Gulp is easier to understand and more efficient
  • Gulp has a JavaScript syntax unlike JSON syntax of Grunt
  • Gulp is faster than Grunt (One of my theme's compile time went from 12secs to 300ms)
  • Gulp is intuitive and gets 20K-25k downloads a day, which is equal to or great than Grunt downloads
  • Gulp's code-over-configuration makes it not only easy to write tasks for, but also much easier to read and maintain

That said, let's leave the story for now and see what it is like to be using Gulp with WordPress.

Do I Need to Use Gulp?

Are you a web developer? Do you program in HTML/CSS sometimes?

— Yes!

Did you ever use a task-runner? Feel like you belong to the stone age while all the crazy kids in town are using awesome advanced workflows?

— Yes! :(.

OK! let's get you started then.

Since this article is about an advanced Gulp based workflow, explaining how to do basic Gulp related stuff is out of the scope. But instead of leaving you (beginners) in the middle of nowhere, I'd like to suggest a few articles to help you get started.


If having a basic Gulp workflow is nothing new for you, then maybe you'll enjoy reading my advanced gulp workflow.

Advanced Gulp WordPress Workflow

While building premium themes for WordPress, I end up with a lot of grunt work which includes:

  • Minifying & optimizing CSS with stylesheets and Sass
  • Custom JavaScripts and 3rd Party Scripts via Bower
  • Synced cross-browser design testing on different devices
  • Tunneling my localhost dev branch to a random public URL for public access
  • Building an installable zip theme file without any node modules, cache files or .Dot files data in it

Let's explore how I get all this stuff done with Gulp.

I have set up a Github repository called Advanced Gulp WordPress. You can check out the structure of files inside it for better understanding.

Using Gulp for WordPress Automation

A Look at gulpfile.js

Now let's take a look at gulpfile.js which is present in the root folder of our theme. I will explain each task one by one to make it easier for you to understand. You'll find completed gulpfile.js at the end of this post.

Since I assume you already know how Gulp works, I will not be explaining how to put all the plugins/packages in package.json file and other basic setup related steps.

Step #1: Setting Up the Variables

First of all I am going to set up certain variables so that I don't have to change everything for every other theme I create. Let's review the code:

/**
 *
 * Gulpfile setup
 *
 * @since 1.0.0
 * @authors Ahmad Awais, @digisavvy, @desaiuditd, @jb510, @dmassiani and @Maxlopez
 * @package neat
 * @forks _s & some-like-it-neat
 */


// Project configuration
var project   = 'neat', // Project name, used for build zip.
 url   = 'neat.dev', // Local Development URL for BrowserSync. Change as-needed.
 bower   = './assets/bower_components/'; // Not truly using this yet, more or less playing right now. TO-DO Place in Dev branch
 build   = './buildtheme/', // Files that you want to package into a zip go here
 buildInclude  = [
    // include common file types
    '**/*.php',
    '**/*.html',
    '**/*.css',
    '**/*.js',
    '**/*.svg',
    '**/*.ttf',
    '**/*.otf',
    '**/*.eot',
    '**/*.woff',
    '**/*.woff2',

    // include specific files and folders
    'screenshot.png',

    // exclude files and folders
    '!node_modules/**/*',
    '!assets/bower_components/**/*',
    '!style.css.map',
    '!assets/js/custom/*',
    '!assets/css/patrials/*'

   ];

I've set up variables for:

  • L#12: Project name
  • L#13: Project URL at localhost
  • L#14: Path to folder of Bower Components
  • L#15: Name of the folder which will be created for building an installable theme zip file
  • L#16: Variable buildInclude contains the paths to files or folders which are to be included and excluded in while building a clean theme inside buildTheme folder for creating a zip file

Step #2: Loading Gulp Plugins

I am using quite a few gulp plugins to help manage everything we discussed above.

// Load plugins
 var  gulp         = require('gulp'),
  browserSync  = require('browser-sync'), // Asynchronous browser loading on .scss file changes
  reload       = browserSync.reload,
  autoprefixer = require('gulp-autoprefixer'), // Autoprefixing magic
  minifycss    = require('gulp-uglifycss'),
  filter       = require('gulp-filter'),
  uglify       = require('gulp-uglify'),
  imagemin     = require('gulp-imagemin'),
  newer        = require('gulp-newer'),
  rename       = require('gulp-rename'),
  concat       = require('gulp-concat'),
  notify       = require('gulp-notify'),
  cmq          = require('gulp-combine-media-queries'),
  runSequence  = require('gulp-run-sequence'),
  sass         = require('gulp-sass'),
  plugins      = require('gulp-load-plugins')({ camelize: true }),
  ignore       = require('gulp-ignore'), // Helps with ignoring files and directories in our run tasks
  rimraf       = require('gulp-rimraf'), // Helps with removing files and directories in our run tasks
  zip          = require('gulp-zip'), // Using to zip up our packaged theme into a tasty zip file that can be installed in WordPress!
  plumber      = require('gulp-plumber'), // Helps prevent stream crashing on errors
  cache        = require('gulp-cache'),
  sourcemaps   = require('gulp-sourcemaps');

The plugins I am using to are as follows:

  • Gulp — Pretty much self-explanatory
  • BrowserSyn — Amazing Time-saving synchronized browser testing
  • AutoPrefixer — Auto prefix CSS for legacy browsers
  • UglifyCSS — For CSS Minification
  • Filter — Enables you to work on a subset of the original files by filtering them using globbing.
  • UglifyJS — Minifies JS files
  • ImageMin — Minifies PNG, JPEG, GIF and SVG images
  • Newer — For passing through only those source files that are newer than corresponding destination files.
  • Rename — To easily rename files
  • Concat — To concatenate JS files
  • Notify — To send notification to OS based on node notifier module
  • Combine Media Queries — To combine repetitive media queries after Sass or Less
  • Run Sequence — Run a series of dependent gulp tasks in order
  • Gulp Sass — Gulp plugin for Sass which is based on libSass
  • Load Plugins — To automatically load in gulp plugins
  • Ignore — To ignore files in the stream based on file characteristics
  • Zip — To zip or compress files
  • Plumber — Fix node pipes, prevent them from breaking due to an error
  • Cache — A cache proxy task for Gulp
  • Source Maps — Source map support for CSS partial files

Phew! That was a lot. Well, at the end of the day, it's all worth the effort.

Step #3: Task to Setup Browser Sync

Let's dive a little deeper and create some awesome tasks to automate our workflow. Browsersync is one of my favorite plugins.

/**
 * Browser Sync
 *
 * Asynchronous browser syncing of assets across multiple devices!! Watches for changes to js, image and php files
 * Although, I think this is redundant, since we have a watch task that does this already.
*/
gulp.task('browser-sync', function() {
 var files = [
   '**/*.php',
   '**/*.{png,jpg,gif}'
      ];
 browserSync.init(files, {

  // Read here http://www.browsersync.io/docs/options/
  proxy: url,

  // port: 8080,

  // Tunnel the Browsersync server through a random Public URL
  // tunnel: true,

  // Attempt to use the URL "http://my-private-site.localtunnel.me"
  // tunnel: "ppress",

  // Inject CSS changes
  injectChanges: true

 });
});

I used to have LiveReload in my workflow, which would reload the web page whenever a file gets edited and saved. But when Browsersync was introduced, it helped me cut down half of the shitty things I had to do in order to automate syncing between different browsers. Browsersync helps me with the following stuff;

  • Injecting CSS changes without any web page getting reloaded
  • Change the port and set up a tunnel through a random public URL, which means my teammates can access the dev branch live at my localhost in a matter of minutes
  • Synced testing across different browsers, where I open the given URL at my Desktop, Laptop, Tab, and mobile to test same features in a single go with synced scroll, clicks, form inputs, etc. Watch the video at Browsersync.

Step #4: Gulp Style Task

Let's take a look at the styles task I built. In this task, there is a lot going on. We are compiling Sass, creating minified CSS and optimizing media queries.

/**
 * Styles
 *
 * Looking at src/sass and compiling the files into Expanded format, Autoprefixing and sending the files to the build folder
 *
 * Sass output styles: https://web-design-weekly.com/2014/06/15/different-sass-output-styles/
*/
gulp.task('styles', function () {
 return      gulp.src('./assets/css/*.scss')
   .pipe(plumber())
   .pipe(sourcemaps.init())
   .pipe(sass({
    errLogToConsole: true,
 
    //outputStyle: 'compressed',
    outputStyle: 'compact',
    // outputStyle: 'nested',
    // outputStyle: 'expanded',
    precision: 10
   }))
   .pipe(sourcemaps.write({includeContent: false}))
   .pipe(sourcemaps.init({loadMaps: true}))
   .pipe(autoprefixer('last 2 version', '> 1%', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
   .pipe(sourcemaps.write('.'))
   .pipe(plumber.stop())
   .pipe(gulp.dest('./'))
   .pipe(filter('**/*.css')) // Filtering stream to only css files
   .pipe(cmq()) // Combines Media Queries
   .pipe(reload({stream:true})) // Inject Styles when style file is created
   .pipe(rename({ suffix: '.min' }))
   .pipe(minifycss({
    maxLineLen: 80
   }))
   .pipe(gulp.dest('./'))
   .pipe(reload({stream:true})) // Inject Styles when min style file is created
   .pipe(notify({ message: 'Styles task complete', onLast: true }))
});

Let me explain line by line, what's going on in there:

  • L#1 – L#8: I created the styles task
  • L#9: Here I provided the source of Sass file, which is a style.scss file present at ./assets/css/style.scss
  • L#10: Initiated plumber to avoid pipe breaking due to minor CSS errors when I save a file
  • L#11: Sourcemaps got initiated. A source map provides a way of mapping code within a compressed file back to its original position in a source file. This means that – with the help of a bit of software – you can easily debug your applications even after your assets have been optimized. The Chrome and Firefox developer tools both ship with built-in support for source maps.
  • L#12 – L#20: Sass is being compiled in compact format, you can read about Sass formats here
  • L#23: I am prefixing CSS with autoprefixer. The legacy browsers which should be supported are mentioned inside the arguments array
  • L#26: Here I have saved the final compiled CSS file for our WordPress theme in the root directory. Since style.scss was being compiled, the output will be style.css file. Now all you need to do is make sure you use a "!" in the comments at the top of the style.scss file so that, even when the compiling/compressing happens, the comment remains: i.e. /*! Theme Name: Your Theme etc... */
  • L#27: I filtered all the .css files in the root directory, which at the moment is one style.css file
  • L#28: Combined the media queries inside selected files
  • L#30: Renamed the file to style.min.css
  • L#31 – L#33: Minified the style.min.css file
  • L#34: Output was saved as a style.min.css file in the root of WP theme folder

Step #5: Scripts Minification and Concatenation

Now to deal with custom JavaScript files and 3rd Party JS files, I have created two tasks called vendorsJs and scriptsJs. They both are pretty much similar. Let's review the code.

/**
 * Scripts: Vendors
 *
 * Look at src/js and concatenate those files, send them to assets/js where we then minimize the concatenated file.
*/
gulp.task('vendorsJs', function() {
 return      gulp.src(['./assets/js/vendor/*.js', bower+'**/*.js'])
   .pipe(concat('vendors.js'))
   .pipe(gulp.dest('./assets/js'))
   .pipe(rename( {
    basename: "vendors",
    suffix: '.min'
   }))
   .pipe(uglify())
   .pipe(gulp.dest('./assets/js/'))
   .pipe(notify({ message: 'Vendor scripts task complete', onLast: true }));
});


/**
 * Scripts: Custom
 *
 * Look at src/js and concatenate those files, send them to assets/js where we then minimize the concatenated file.
*/

gulp.task('scriptsJs', function() {
 return      gulp.src('./assets/js/custom/*.js')
   .pipe(concat('custom.js'))
   .pipe(gulp.dest('./assets/js'))
   .pipe(rename( {
    basename: "custom",
    suffix: '.min'
   }))
   .pipe(uglify())
   .pipe(gulp.dest('./assets/js/'))
   .pipe(notify({ message: 'Custom scripts task complete', onLast: true }));
});

Here is what's happening in there:

  • I selected the vendor JS files paths with bower path at L#7 and the custom JS files paths at L#27
  • Concatenated the files to two single files called vendors.js and custom.js
  • Saved the output in ./assets/js/ folder
  • Renamed the files to with .min suffix
  • Minified/uglified the files
  • And saved two more files called vendors.min.js and custom.js

Step #6: Image Optimization Task

There nothing new in the image optimization task. Look at the code below.

/**
 * Images
 *
 * Look at src/images, optimize the images and send them to the appropriate place
*/
gulp.task('images', function() {

// Add the newer pipe to pass through newer images only
 return  gulp.src(['./assets/img/raw/**/*.{png,jpg,gif}'])
    .pipe(newer('./assets/img/'))
    .pipe(rimraf({ force: true }))
    .pipe(imagemin({ optimizationLevel: 7, progressive: true, interlaced: true }))
    .pipe(gulp.dest('./assets/img/'))
    .pipe( notify( { message: 'Images task complete', onLast: true } ) );
});

  • I selected the ./assets/img/raw/**/*.{png,jpg,gif} path as a source
  • Optimized the images and placed them in ./assets/img/ folder

Step #7: Building a Clean Installable Theme Zip File

This is a group of tasks which are responsible for creating a clean copy of installable theme zip file. Take a quick look at the code. There is nothing much to it, it is all basic copy pasting and deleting or ignoring of files/folders.

/**
 * Clean gulp cache
 */
 gulp.task('clear', function () {
   cache.clearAll();
 });


 /**
  * Clean tasks for zip
  *
  * Being a little overzealous, but we're cleaning out the build folder, codekit-cache directory and annoying DS_Store files and Also
  * clearing out unoptimized image files in zip as those will have been moved and optimized
 */

 gulp.task('cleanup', function() {
  return  gulp.src(['./assets/bower_components', '**/.sass-cache','**/.DS_Store'], { read: false }) // much faster
   .pipe(ignore('node_modules/**')) //Example of a directory to ignore
   .pipe(rimraf({ force: true }))
   // .pipe(notify({ message: 'Clean task complete', onLast: true }));
 });
 gulp.task('cleanupFinal', function() {
  return  gulp.src(['./assets/bower_components','**/.sass-cache','**/.DS_Store'], { read: false }) // much faster
   .pipe(ignore('node_modules/**')) //Example of a directory to ignore
   .pipe(rimraf({ force: true }))
   // .pipe(notify({ message: 'Clean task complete', onLast: true }));
 });

 /**
  * Build task that moves essential theme files for production-ready sites
  *
  * buildFiles copies all the files in buildInclude to build folder - check variable values at the top
  * buildImages copies all the images from img folder in assets while ignoring images inside raw folder if any
  */

  gulp.task('buildFiles', function() {
   return  gulp.src(buildInclude)
    .pipe(gulp.dest(build))
    .pipe(notify({ message: 'Copy from buildFiles complete', onLast: true }));
  });


/**
* Images
*
* Look at src/images, optimize the images and send them to the appropriate place
*/
gulp.task('buildImages', function() {
 return  gulp.src(['assets/img/**/*', '!assets/images/raw/**'])
   .pipe(gulp.dest(build+'assets/img/'))
   .pipe(plugins.notify({ message: 'Images copied to buildTheme folder', onLast: true }));
});

 /**
  * Zipping build directory for distribution
  *
  * Taking the build folder, which has been cleaned, containing optimized files and zipping it up to send out as an installable theme
 */
 gulp.task('buildZip', function () {
  // return  gulp.src([build+'/**/', './.jshintrc','./.bowerrc','./.gitignore' ])
  return  gulp.src(build+'/**/')
   .pipe(zip(project+'.zip'))
   .pipe(gulp.dest('./'))
   .pipe(notify({ message: 'Zip task complete', onLast: true }));
 });


 // ==== TASKS ==== //
 /**
  * Gulp Default Task
  *
  * Compiles styles, fires-up browser sync, watches js and php files. Note browser sync task watches php files
  *
 */

 // Package Distributable Theme
 gulp.task('build', function(cb) {
  runSequence('styles', 'cleanup', 'vendorsJs', 'scriptsJs',  'buildFiles', 'buildImages', 'buildZip','cleanupFinal', cb);
 });

This task will run only when I will use gulp build command. Let's review what happens when this command is run:

  • clear: Task to clear the gulp cache
  • cleanup: Task to remove sass-cache, bower_components and .DS_Store files in the stream. It also ignores the node_modules folder
  • cleanupFinal: Another cleanup task to be run at the end of build sequence. It may or may not have any difference from cleanup task
  • buildFiles: Build task that moves essential theme files to the buildTheme folder
  • buildImages: Task to build and copy the final set of images to the buildTheme folder
  • buildZip: Task to create an installable Zip file of the buildTheme folder
  • build: Main task which runs styles task to compile CSS then cleans up everything with a cleanup task. After that, it runs all the scripts related and build related tasks and a final cleanup task at the end to produce a theme zip file via buildTheme folder.

Step #8: Watch Task

Finally, there is a watch task which helps in automatically running the styles, scripts, images and browser-sync related tasks in case of any change that occurs in the below-mentioned folder.

// Watch Task
 gulp.task('default', ['styles', 'vendorsJs', 'scriptsJs', 'images', 'browser-sync'], function () {
  gulp.watch('./assets/img/raw/**/*', ['images']); 
  gulp.watch('./assets/css/**/*.scss', ['styles']);
  gulp.watch('./assets/js/**/*.js', ['scriptsJs', browserSync.reload]);

 });

Final gulpfile.js

/**
 *
 * Gulpfile setup
 *
 * @since 1.0.0
 * @authors Ahmad Awais, @digisavvy, @desaiuditd, @jb510, @dmassiani and @Maxlopez
 * @package neat
 * @forks _s & some-like-it-neat
 */


// Project configuration
var project   = 'neat', // Project name, used for build zip.
 url    = 'neat.dev', // Local Development URL for BrowserSync. Change as-needed.
 bower    = './assets/bower_components/'; // Not truly using this yet, more or less playing right now. TO-DO Place in Dev branch
 build    = './buildtheme/', // Files that you want to package into a zip go here
 buildInclude  = [
      // include common file types
      '**/*.php',
      '**/*.html',
      '**/*.css',
      '**/*.js',
      '**/*.svg',
      '**/*.ttf',
      '**/*.otf',
      '**/*.eot',
      '**/*.woff',
      '**/*.woff2',

      // include specific files and folders
      'screenshot.png',

      // exclude files and folders
      '!node_modules/**/*',
      '!assets/bower_components/**/*',
      '!style.css.map',
      '!assets/js/custom/*',
      '!assets/css/patrials/*'

     ];

// Load plugins
 var gulp     = require('gulp'),
  browserSync  = require('browser-sync'), // Asynchronous browser loading on .scss file changes
  reload       = browserSync.reload,
  autoprefixer = require('gulp-autoprefixer'), // Autoprefixing magic
  minifycss    = require('gulp-uglifycss'),
  filter       = require('gulp-filter'),
  uglify       = require('gulp-uglify'),
  imagemin     = require('gulp-imagemin'),
  newer        = require('gulp-newer'),
  rename       = require('gulp-rename'),
  concat       = require('gulp-concat'),
  notify       = require('gulp-notify'),
  cmq          = require('gulp-combine-media-queries'),
  runSequence  = require('gulp-run-sequence'),
  sass         = require('gulp-sass'),
  plugins      = require('gulp-load-plugins')({ camelize: true }),
  ignore       = require('gulp-ignore'), // Helps with ignoring files and directories in our run tasks
  rimraf       = require('gulp-rimraf'), // Helps with removing files and directories in our run tasks
  zip          = require('gulp-zip'), // Using to zip up our packaged theme into a tasty zip file that can be installed in WordPress!
  plumber      = require('gulp-plumber'), // Helps prevent stream crashing on errors
  cache        = require('gulp-cache'),
  sourcemaps   = require('gulp-sourcemaps');


/**
 * Browser Sync
 *
 * Asynchronous browser syncing of assets across multiple devices!! Watches for changes to js, image and php files
 * Although, I think this is redundant, since we have a watch task that does this already.
*/
gulp.task('browser-sync', function() {
 var files = [
     '**/*.php',
     '**/*.{png,jpg,gif}'
    ];
 browserSync.init(files, {

  // Read here http://www.browsersync.io/docs/options/
  proxy: url,

  // port: 8080,

  // Tunnel the Browsersync server through a random Public URL
  // tunnel: true,

  // Attempt to use the URL "http://my-private-site.localtunnel.me"
  // tunnel: "ppress",

  // Inject CSS changes
  injectChanges: true

 });
});



/**
 * Styles
 *
 * Looking at src/sass and compiling the files into Expanded format, Autoprefixing and sending the files to the build folder
 *
 * Sass output styles: https://web-design-weekly.com/2014/06/15/different-sass-output-styles/
*/
gulp.task('styles', function () {
  gulp.src('./assets/css/*.scss')
  .pipe(plumber())
  .pipe(sourcemaps.init())
  .pipe(sass({
   errLogToConsole: true,
 
   //outputStyle: 'compressed',
   outputStyle: 'compact',
   // outputStyle: 'nested',
   // outputStyle: 'expanded',
   precision: 10
  }))
  .pipe(sourcemaps.write({includeContent: false}))
  .pipe(sourcemaps.init({loadMaps: true}))
  .pipe(autoprefixer('last 2 version', '> 1%', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
  .pipe(sourcemaps.write('.'))
  .pipe(plumber.stop())
  .pipe(gulp.dest('./'))
  .pipe(filter('**/*.css')) // Filtering stream to only css files
  .pipe(cmq()) // Combines Media Queries
  .pipe(reload({stream:true})) // Inject Styles when style file is created
  .pipe(rename({ suffix: '.min' }))
  .pipe(minifycss({
   maxLineLen: 80
  }))
  .pipe(gulp.dest('./'))
  .pipe(reload({stream:true})) // Inject Styles when min style file is created
  .pipe(notify({ message: 'Styles task complete', onLast: true }))
});


/**
 * Scripts: Vendors
 *
 * Look at src/js and concatenate those files, send them to assets/js where we then minimize the concatenated file.
*/
gulp.task('vendorsJs', function() {
 return  gulp.src(['./assets/js/vendor/*.js', bower+'**/*.js'])
    .pipe(concat('vendors.js'))
    .pipe(gulp.dest('./assets/js'))
    .pipe(rename( {
     basename: "vendors",
     suffix: '.min'
    }))
    .pipe(uglify())
    .pipe(gulp.dest('./assets/js/'))
    .pipe(notify({ message: 'Vendor scripts task complete', onLast: true }));
});


/**
 * Scripts: Custom
 *
 * Look at src/js and concatenate those files, send them to assets/js where we then minimize the concatenated file.
*/

gulp.task('scriptsJs', function() {
 return  gulp.src('./assets/js/custom/*.js')
    .pipe(concat('custom.js'))
    .pipe(gulp.dest('./assets/js'))
    .pipe(rename( {
     basename: "custom",
     suffix: '.min'
    }))
    .pipe(uglify())
    .pipe(gulp.dest('./assets/js/'))
    .pipe(notify({ message: 'Custom scripts task complete', onLast: true }));
});


/**
 * Images
 *
 * Look at src/images, optimize the images and send them to the appropriate place
*/
gulp.task('images', function() {

// Add the newer pipe to pass through newer images only
 return  gulp.src(['./assets/img/raw/**/*.{png,jpg,gif}'])
    .pipe(newer('./assets/img/'))
    .pipe(rimraf({ force: true }))
    .pipe(imagemin({ optimizationLevel: 7, progressive: true, interlaced: true }))
    .pipe(gulp.dest('./assets/img/'))
    .pipe( notify( { message: 'Images task complete', onLast: true } ) );
});


/**
 * Clean gulp cache
 */
 gulp.task('clear', function () {
   cache.clearAll();
 });


 /**
  * Clean tasks for zip
  *
  * Being a little overzealous, but we're cleaning out the build folder, codekit-cache directory and annoying DS_Store files and Also
  * clearing out unoptimized image files in zip as those will have been moved and optimized
 */

 gulp.task('cleanup', function() {
  return  gulp.src(['./assets/bower_components', '**/.sass-cache','**/.DS_Store'], { read: false }) // much faster
     .pipe(ignore('node_modules/**')) //Example of a directory to ignore
     .pipe(rimraf({ force: true }))
     // .pipe(notify({ message: 'Clean task complete', onLast: true }));
 });
 gulp.task('cleanupFinal', function() {
  return  gulp.src(['./assets/bower_components','**/.sass-cache','**/.DS_Store'], { read: false }) // much faster
     .pipe(ignore('node_modules/**')) //Example of a directory to ignore
     .pipe(rimraf({ force: true }))
     // .pipe(notify({ message: 'Clean task complete', onLast: true }));
 });

 /**
  * Build task that moves essential theme files for production-ready sites
  *
  * buildFiles copies all the files in buildInclude to build folder - check variable values at the top
  * buildImages copies all the images from img folder in assets while ignoring images inside raw folder if any
  */

  gulp.task('buildFiles', function() {
   return  gulp.src(buildInclude)
      .pipe(gulp.dest(build))
      .pipe(notify({ message: 'Copy from buildFiles complete', onLast: true }));
  });


/**
* Images
*
* Look at src/images, optimize the images and send them to the appropriate place
*/
gulp.task('buildImages', function() {
 return  gulp.src(['assets/img/**/*', '!assets/images/raw/**'])
     .pipe(gulp.dest(build+'assets/img/'))
     .pipe(plugins.notify({ message: 'Images copied to buildTheme folder', onLast: true }));
});

 /**
  * Zipping build directory for distribution
  *
  * Taking the build folder, which has been cleaned, containing optimized files and zipping it up to send out as an installable theme
 */
 gulp.task('buildZip', function () {
  // return  gulp.src([build+'/**/', './.jshintrc','./.bowerrc','./.gitignore' ])
  return  gulp.src(build+'/**/')
     .pipe(zip(project+'.zip'))
     .pipe(gulp.dest('./'))
     .pipe(notify({ message: 'Zip task complete', onLast: true }));
 });


 // ==== TASKS ==== //
 /**
  * Gulp Default Task
  *
  * Compiles styles, fires-up browser sync, watches js and php files. Note browser sync task watches php files
  *
 */

 // Package Distributable Theme
 gulp.task('build', function(cb) {
  runSequence('styles', 'cleanup', 'vendorsJs', 'scriptsJs',  'buildFiles', 'buildImages', 'buildZip','cleanupFinal', cb);
 });


 // Watch Task
 gulp.task('default', ['styles', 'vendorsJs', 'scriptsJs', 'images', 'browser-sync'], function () {
  gulp.watch('./assets/img/raw/**/*', ['images']); 
  gulp.watch('./assets/css/**/*.scss', ['styles']);
  gulp.watch('./assets/js/**/*.js', ['scriptsJs', browserSync.reload]);

 });

Sunday, December 20, 2015

6 Essential Bourbon Neat Mixins

6 Essential Bourbon Neat Mixins

This tutorial is aimed at getting you started with Neat; a semantic grid framework built for Sass and Bourbon.

Wednesday, November 25, 2015

Creating an Animated Menu Indicator with CSS Selectors

Animated Menu Indicator with CSS Selectors

In this article, I'll cover creative ways of using sibling selectors and pseudo elements to make a CSS-only menu indicator that would normally be achieved using JavaScript.

Saturday, November 07, 2015

Animated Intro Section

Animated Intro Section

A collection of fancy text effects, to animate the tagline and action buttons of your website intro section.

Animations in web design are often used to drive the user's focus to a specific section. One section you want to make sure to highlight is the main tagline, with the action buttons.

Saturday, November 07, 2015

Synchronous and asynchronous sequential execution of functions

Synchronous and asynchronous sequential execution of functions

This blog post examines three ways of executing function sequentially:

  • Synchronously
  • Asynchronously, via Promises
  • Asynchronously, via the library co

Tuesday, October 27, 2015

Chrome Devtools Tips & Tricks

Chrome Devtools Tips & Tricks

Lately I've spent more time than usual using the Chrome Developer Tools. Along the way I found a couple of nice features that I had previously missed (or at least hadn't needed enough to look for; e.g. blackboxing and asynchronous stacktraces). Because of this, I wanted to summarized a few of the features that I really like about devtools.

1) The little magnifier glass icon that shows you which CSS class/selector in what CSS file that ultimately decides the styling for a specific element and CSS property. For example, select "inspect" on any DOM element and then switch to the "Computed" sub-tab to the right. Find the CSS property you're interested in and clicking the magnifier icon takes you straight to the correct CSS class/selector in the right .css file (very useful when you start out working on a new large web app):

Chrome Devtools Tips & Tricks

2) To see what XHRs the web app sends while you're using it, check the "Log XMLHttpRequests" checkbox under "Settings" and keep an eye on the console tab. Before I knew about this, I would set up my browser to go through an HTTP intercept proxy like Burp suite but this more convenient if you just want an quick overview. Of course, with an intercept proxy you also get the opportunity to modify the XHRs before they reach the server, which is really nice for security testing. A lightweight alternative to that is to look under "Sources :: XHR Breakpoints" and activate the "Any XHR" breakpoint.

Chrome Devtools Tips & Tricks

3) Now, suppose that the web app you're working on is making some XHR at a regular interval (for example, keeping the current view up to date) and you would like to know where this timer is setup (i.e. where the call to setTimeout() or possibly setInterval() is made). To figure this out, you switch to the "Sources" tab, and check the "Async" checkbox. This will make all your stacktraces continue beyond setTimeout() and friends, even multiple levels deep. It does the same thing for requestAnimationFrame() and addEventListener() and a few others too. You'll find the checkbox here:

Chrome Devtools Tips & Tricks

4) To quickly find the code that runs when you click a particular button or link is clicked, activate an "Event listener breakpoint" for Mouse :: Click just before you click the particular button (another killer feature when starting out work on an existing large web app):

Chrome Devtools Tips & Tricks

5) When you use "Event listener breakpoint :: Mouse :: Click" you might end up in a third-party library like jQuery at first, and so you'd have to step a few times in the debugger to arrive at the "real" event handler. A great way to avoid this is to "blackbox" the third-party script. The debugger will never stop inside a blackboxed script, instead it continues to run until it reaches a line that isn't in a blackboxed file. You can blackbox a script by right clicking the filename of the third-party library in the callstack and the selecting "Blackbox Script" from the context menu:

Chrome Devtools Tips & Tricks

6) You can jump to a specific file, by name, using ctrl-p (just like in atom):

Chrome Devtools Tips & Tricks

7) You can jump to a function, by name (but only in the currently open file), using ctrl-shift-p:

Chrome Devtools Tips & Tricks

8) You can search through all files using ctrl-shift-f:

Chrome Devtools Tips & Tricks

9) You can edit using multiple simultaneous cursors by selecting some word and then pressing ctrl-d a few times to select more instances of that word (again, just like in atom). Very nice for renaming variables:

Chrome Devtools Tips & Tricks

10) When working on a website stored locally it's possible to edit files in devtools and save the changes directly to disk. To do this, switch to the Sources tab, right click on the Sources sub-tab and select "Add Folder to Workspace" and then finally select the local folder where your project is located. After that, right click the local copy of some file in your site and select "Map to Network Resource..." and then select the corresponding "network" file:

Chrome Devtools Tips & Tricks

Other handy tips include:

  • $0 in console is the element you selected in the elements view.
  • You can evaluate XPath expressions using $x("//p") (very useful if you're writing selenium testcases and CSS selectors doesn't get you all the way).

I also recommend that you install two Chrome extensions:

  • JSONView will indent and syntax highlight JSON blobs for you (and even allow you to expand/collapse blocks). It also makes URLs inside JSON clickable which often makes it possible to explore a JSON-based API via the browser. For example, try navigating to http://omahaproxy.appspot.com/all.json before and after you install it (better formatting) and also https://api.github.com/ (clickable URLs make it easier to explore the API).
  • JS Error Notifier (non-"spyware" version) creates a popup each time a Javascript error is printed to the console. Unfortunately, the main version of this extension submits private “usage data” to a third-party service (see discussion in issue #28). But at any rate, this extension has helped me notice and fix several bugs.

All in all I really like devtools, the only annoying thing that I can think of is that you cannot customize keybindings:


Thursday, October 22, 2015

The Art of Debugging

The Art of Debugging

Before we start though...

How to skip to the end...
Don't.

Write.

Bugs.

Monday, October 19, 2015

Diamond grid layout with Sass

Diamond grid layout with Sass

Since I started my career on the web, I've been building websites that follow standard grid layouts. It got to a point where I was telling an intern at my company that developers think in rectangles. I mean, there's nothing wrong with rectangular layouts. They're like your mom's Volvo, steady and reliable. But sometimes, it's fun to try something different. I'm lucky enough to work with some awesome designers, and for a new project, they came up with a diamond-based grid layout. Well then, challenge accepted. (•̀o•́)ง

Tuesday, October 13, 2015

Async/Await: The Hero JavaScript Deserved

Async/Await: The Hero JavaScript Deserved

Writing asynchronous code is hard. When it comes to JavaScript we rely heavily on callback functions to accomplish asynchronous tasks which can be unintuitive. This cognitive overhead creates a barrier to entry for newcomers to programming and the language and even causes frequent heartburn for those of us who have been using the language a while.

Tuesday, October 06, 2015

Responsive Images for Busy People: Srcset & Sizes

Responsive Images

This guide won't regale you with the cool theory behind responsive images, because for now, you're just testing the waters. It won't lecture you on the potential benefits to site speed, because there are better resources out there. And it won't dwell on the pitfalls, quirks, or brain-bending complexities of the modern implementation, because you're a busy person and will study these later.

Instead, we're going to look at a single real-life example of efficient image scaling using the new srcset and sizes attributes. These should not be used for art direction – for this, you'll want to use picture and source media. Instead, we're strictly concerned with ensuring that the user's browser loads the most appropriately-sized version of our image based on the size of the viewport. We want the browser to be able to pick a source file before parsing or rendering CSS and JavaScript. To do this, we'll need to supply it with:

  • a list of image source files, plus their widths in pixels/li>
  • hints about the size at which the image will be rendered on the page

A Live Example

We'll dive right in with a live example, and then break down how it works. For the sake of being topical (and because NASA / JPL kindly release their images into the public domain), we'll go with the Curiosity Rover's selfie on Mars. The three image source files have been watermarked for convenience. Take a peek at the HTML and CSS powering this example. What's going on here? The first thing to note is that the usual src and alt attributes are present and correct. Browsers which don't support the new syntax will simply load the resource specified in the src as a fallback.

See the Pen Responsive Images with srcset and sizes by Tom Bennet (@tombennet) on CodePen.

srcset

Next up, srcset. As you'll see, this attribute contains a comma-separated list of image URLs; each one is followed by its width in pixels, specified using the w descriptor. "Why can't we specify heights too?", I hear you ask. Well, most images in responsive environments are constrained by their widths, not their heights, so dealing solely in widths keeps things simple. This situation may change in the future, but not today. So, our browser now knows which resources it can choose from. Splendid. There is, however, a problem: the browser doesn't know the size at which the image will be rendered on the page until it has downloaded and parsed all the relevant CSS and JavaScript files. Relaying this information to the browser before it starts processing your page's layout will enable it to start downloading the appropriate image file as soon as possiblw.

sizes

This is where sizes comes into play. This attribute is used to describe the various sizes at which the image will be rendered on the page. Much like srcset, it takes a comma-separated list, but in this case we supply a list of CSS lengths paired with media conditions, which is then followed by a default length. This is easier to understand when you've got a simple example translated into English, so here goes...
sizes="(min-width: 40em) 33.3vw, 100vw"
This says: "If the viewport is wider than 40em, then this image will take up one third of the viewport. Otherwise, it will take up the full-width (100%) of the viewport." We're pairing media conditions with CSS lengths, thereby giving the browser a clue as to the breakpoints it will encounter in the CSS. Note that more than one pair can be provided. The browser will iterate through your list of media conditions until it finds one that matches, and will then use the paired length as the image's rendered width. If none match, it will use the last length in your list (the one without a paired query) as a default. Here's the syntax:
sizes="
[media condition] [length],
[media condition] [length],
[media condition] [length],
[default length]"
Our browser now has all the information it needs to make a decision; it knows the widths of the source files, and the size at which the image will be rendered on our page.

calc

Why, then, is our Martian example slightly more complex? What is calc doing there? On the Red Planet, as on Earth, determining the size of a single element can be complex in a responsive environment. Thankfully, we can include the excellent and widely-supported CSS calc function to describe a relative length as mathematical expression. In our example, we've used calc(66.6vw - 4em) to express the length: "Two thirds of the viewport width, minus 4ems." Sure enough, head into the CSS, and you'll see rules that set our image width at 66.6% with 2em padding on either side.

Final Thoughts

A few pointers for those of you frantically resizing and refreshing the CodePen to try and change the watermark: the spec is fairly flexible, and allows the browser to make decisions based on both your markup and on other relevant factors. These can include the user's connection, preferences, whether or not a larger asset is already cached, and so on. Something to bear in mind. Support for this technique is quickly gaining momentum. As of the launch of OS X El Capitan, a full implementation is available in the current versions of Chrome, Firefox, Safari, and Opera (including mobile versions). As for Microsoft Edge, it's available in version 13, currently in Insider Preview and due to be released soon. For developers who require wider browser support, Picturefill is an excellent polyfill for responsive images. Version 3.0 – a full rewrite of the codebase – will be available imminently. That's all for now. If this quick primer has tickled your fancy, be sure to check out the excellent resources below for a comprehensive introduction. Happy coding!

Resources