Showing posts with label sass. Show all posts
Showing posts with label sass. Show all posts

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.

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, July 07, 2015

Responsive Typography With Sass Maps

Responsive Typography With Sass Maps
Managing consistent, typographic rhythm isn't easy, but when the type is responsive, things get even more difficult. Fortunately, Sass maps make responsive typography much more manageable.

Writing the code is one thing, but keeping track of font-size values for each breakpoint is another — and the above is for paragraphs alone. Throw in h1 to h6s, each with variable font sizes for each breakpoint, and it gets cumbersome, especially when the type doesn't scale linearly.

Tuesday, April 14, 2015

Understanding Variable Scope in Sass

Variable Scope in Sass
In this article, we'll take a deeper look at variables and variable scope in Sass. The scope of a variable describes the context within which it's defined and therefore where it's available to use.

To start, I'll cover which scopes Sass supports. Then, I'll explain two useful flags we can use to customize the value of a variable. Finally, I'll briefly present the available functions for checking whether a variable exists or not.

Sass Variable Scope

Sass supports two types of variables: local variables and global variables.

By default, all variables defined outside of any selector are considered global variables. That means they can be accessed from anywhere in our stylesheets. For instance, here's a global variable:

$bg-color: green;

On the other hand, local variables are those which are declared inside a selector. Later, we'll examine how we can customize that behavior. But for now, let's see our first example.

Here we define a mixin and then the btn-bg-color variable within it. This is a local variable, and is therefore visible only to the code inside that mixin:

@mixin button-style {
    $btn-bg-color: lightblue;
    color: $btn-bg-color;
}

Next, we can call the mixin as follows:

button { @include button-style; }

The resulting CSS:

button { color: lightblue; }

Imagine, however, that we also want to use this variable (not the mixin) in another selector:

.wrap { background: $btn-bg-color; }

This would give us the following error:

Undefined variable: "$btn-bg-color".

That was to be expected, right? We tried to access a mixin variable, which is locally scoped. Don't worry though, as mentioned above, we'll fix this issue in an upcoming section.

Nested Selectors

It's worth also mentioning that if we declare a variable inside a selector, any other nested selector can access it. Here's an example:

.wrap {
    $bg-color: red;
    &:after {
        background: lighten($bg-color, 10%);
    }
}

This compiles to:

.wrap:after { background: #ff3333; }

However, look at the example below where we define a function, then use that function along with a nested selector:

@function my-function() {
    $text-color: black;
    @return $text-color;
}
     
.wrap {
    color: my-function();
    &:after{
        background: $text-color;
    }
}

If we try to compile this, we'll get the same error discussed before. Again, that happens because we can't access the text-color variable. It isn't directly defined within the parent selector, but inside the function that our selector calls.

Variable Names

Global and local variables can have the same names. To demonstrate that behavior, we'll work on a fourth example:

$text-color: tomato;
 
@mixin button-style {
    $text-color: lime;
    color: $text-color;
}
     
@mixin link-style {
    $text-color: black;
    color: $text-color;
}

Here we've defined three different variables (text-color) with the same name. The first one is a global variable, while the other two are local.

Here are some styles making use of them:

button { @include button-style; }
a { @include link-style; }
.wrap { background: $text-color; }

And the generated CSS:

button { color: lime; }
a { color: black; }
.wrap { background: tomato; }

Is that what you were expecting?

Keep in mind that we won't see these styles unless we compile with the current version of Sass (3.4). For example, supposing that we use Sass 3.3, our CSS output would look like this:

button { color: lime; }
a { color: black; }
.wrap { background: black; }

Notice the difference in the background color of the .wrap selector. This happens because according to the earlier Sass versions (same for LibSass), if we locally redefine the value of a global variable (e.g. text-color), this will be the variable's new (global) value. So, in our example the compiled styles depend on the order we declare the variable and the mixins.

The default flag

This flag allows us to set the value of a variable in case it hasn't already been set or its current value is null (treated as unassigned). To better explain how we can take advantage of it in a real scenario, let's suppose that we have a project with the following structure:

Project-Name/
├── ...
├── css/
│ └── app.css
└── scss/
├── _config.scss
├── _variables.scss
├── _mixins.scss
└── app.scss

The app.scss file looks like this:

@import "config";
@import "variables";
@import "mixins";
     
button { @include button-style; }
 
// more styles

Let's see the contents of the partial files.

Firstly, the variables.scss file contains our variables:

$btn-bg-color: lightblue !default;
$btn-bg-color-hover: darken($btn-bg-color, 5%);
     
// more variables

Notice the default flag assigned to the btn-bg-color variable.

Secondly, the mixins.scss file includes our mixins:

@mixin button-style ($bg-color: $btn-bg-color, $bg-color-hover: $btn-bg-color-hover) {
    background-color: $bg-color;
    // more styles
    &:hover {
        background-color: $bg-color-hover;
        // more styles  
    }
}
     
// more mixins

Then, the generated app.css file will be as follows:

button { color: lightblue; }
button:hover { background-color: #99cfe0; }

So, our buttons come with default styles. But let's suppose that we want to have the option to overwrite them by applying our custom values. To do this, we can reassign the desired (default) variables in the config.scss partial file:

$btn-bg-color: chocolate;
// more variables

Setting the value of this variable to chocolate will result in ignoring the corresponding value (lightblue) that has received the default flag. Therefore, the generated CSS changes as we can see below:

button { color: chocolate; }
button:hover { background-color: #bc5e1b; }

Note: in case we haven't added the default flag to the btn-bg-color variable, our CSS would be, due to the cascading nature of CSS, as follows:

button { color: lightblue; }
// hover styles

The global flag

This second flag helps us change the scope of a local variable.

Do you remember the error we saw in our first example? Well, that happened because we tried to use the btn-bg-color variable in the .wrap selector. Let's modify our example to include this new flag. Here are the new styles:

@mixin button-style {
    $btn-bg-color: lightblue !global;
    color: $btn-bg-color;
}

button { @include button-style; }

.wrap { background: $btn-bg-color; }

As you can see below, thanks to this flag, the CSS compiles without any errors:

button { color: lightblue; }
.wrap { background: lightblue; }

The global flag is useful, but bear in mind that it's not always good practice to change a variable's scope.

Checking if a Variable Exists

Sass provides two introspection functions for testing whether a variable exists or not. We can use the variable-exists and/or global-variable-exists functions to check if our local and/or global variables exist respectively.

For example, here's a common use case where we define a variable containing the absolute path to a Google Font. Then, we choose to import that font in our stylesheets, but only if the relevant variable has been instantiated.

$google-font: "http://fonts.googleapis.com/css?family=Alegreya";

@if(global-variable-exists(google-font)) {
    @import url($google-font);
}

The result:

@import url("http://fonts.googleapis.com/css?family=Alegreya");

Conclusion

In this article, I introduced you to the concept of variable scope in Sass. To make things clearer we looked at different examples, so hopefully you now have a good understanding of how scope works. You can find all the examples of this article in this SassMeister gist.

Tuesday, February 24, 2015

The Problem With Raw CSS

The Problem With Raw CSS

Writing raw CSS is not a pleasure nowadays. Pure CSS has a very simple and, as a side effect, very inflexible syntax that doesn't always suited our needs in building high quality products. The overall complexity of systems are increasing day by day and modern web developers need more and that's why CSS preprocessors exist.

The Overall Structure

In order to explain what a CSS preprocessor is and why we should use this kind of tools I'll build a little demo with raw CSS. After that I'll take this further by translating it in sass, the preprocessor of our choice for this article. This demo will be big enough to see the difference between each tool. I'll try also to explain along the way some new concepts about preprocessors pretty quickly, so that you can start using those right now.

For the rest of this article I'll use this HTML structure:

...

I'll skip the repetition from the code, just to be clear. You can taste full code listing and the demo in this codepen. Later we'll discuss about implementing this demo using sass, the preprocessor we used to talk about in this article.

See the Pen yyvBVZ by Andrei Glingeanu (@andreiglingeanu) on CodePen.


Sass: Syntactically Awesome Style Sheets

Installation

We have 2 options: to use command line or a GUI application.

If you have ruby installed on your system, be it windows, osx or linux, you can type into command line this line to install sass:

gem install sass

Now you should have sass binary on your computer. You can compile a file named input.sass into corresponding output.css using command below. We have --watch switch to indicate that we want to recompile automatically as we change the input.sass file.

sass --watch input.sass:output.css

Another option will be to use a tool like Prepros to compile sass files for you. Many other options at Official Installation Page.

Nesting rules

The most used feature that preprocessor gives us is rules nesting, just like we nest elements in each other in HTML. For example, when we write a rule like this:

#main-content {
    h1 {
        em {
            text-transform: uppercase;
        }
    }
}

it will generate the following CSS:

#main-content h1 em {
    text-transform: uppercase;
}

Using variables

We can treat variables as a named container, where we can put several things. For example we can store a color in a variable and use this color in multiple places. Storing a size in a variable, be it in pixels, percents or ems, is also a good idea. Let's see a demo using variables next.

$itemBackground: #867e7e;
$itemPadding: 5px;

ul.menu {
  list-style: none;
  padding: 0;
  
  li {
    display: inline-block;

    background: $itemBackground;
    transition: background .5s ease-in;
    
    // we can use several functions to modify our colors
    // here we lighten $itemBackground by 50% in order to ensure
    // that we are able to read the text
    color: lighten($itemBackground, 50%);
    
    padding: $itemPadding;
    // we can use arithmetic operations, if variable is a number
    margin-right: $itemPadding * 2;
    
    cursor: pointer;
    font-size: 2em;
  }

}


See how I defined variable $itemBackground in order to store background color for our menu? This is a very common pattern to define our palette of colors at the top and then use this colors throughout the file by explicit name. In this way we can change all colors very easily. In this demo we also have used rules nesting.

Another trick about storing colors in variables is that we can modify them using several predefined functions like:

  • lighten
  • darken
  • saturate
  • alternate

You can see full list of them and their usage on this great resource.

Parent reference

Sometimes, when using rules nesting we need a way to reference the element inside we nest rules. Let's say we need a way to target paragraphs inside an article, but only those who have the class special. We can express it this way:

article {
    p {
        color: #333;

        &.special {
            color: #f00;
        }
    }
}

The key idea here is to use & in order to reference the current element. Sass will give us back the following css:

article p {
    color: #333;
}

article p.special {
    color: #f00;
}

We can use this trick in order to target all the pseudo elements and pseudo classes of current element together. In this demo you'll see how I target :last-child in order to give it margin-right: 0; and also how to style :hover pseudo class for lis using &.

$itemBackground: #867e7e;
$itemPadding: 5px;

ul.menu {
  list-style: none;
  padding: 0;
  
  li {
    display: inline-block;

    background: $itemBackground;
    transition: background .5s ease-in;
    
    // we can use several functions to modify our colors
    // here we lighten $itemBackground by 50% in order to ensure
    // that we are able to read the text
    color: lighten($itemBackground, 50%);
    
    padding: $itemPadding;
    // we can use arithmetic operations, if variable is a number
    margin-right: $itemPadding * 2;
    
    cursor: pointer;
    font-size: 2em;
    
        
    &:hover {
      // another function which modifies color
      background: darken($itemBackground, 10%);
    }

    &:last-child {
      margin-right: 0;
    }
  }
  

}


Mixins and extending rules

In a nutshell, mixins are a way to encapsulate many useful properties into one block, and then using @include insert this properties into our rule. This is somewhat a similar concept to functions in JavaScript and PHP. The main idea is that you can pass different parameters to the mixin, and it will return you the right properties, depending on the parameters you passed. Let's see an example where we define a mixin in order to write vendor prefixes for transform property.

@mixin transform($transformation) {
  -webkit-transform: $transformation;
  transform: $transformation;
}

div {
  width: 100px;
  height: 100px;
  background: red;

  @include transform(translate(40px));
}


Let's see, here we have transform mixin, and we include it into div, using @include. As a named parameter $transformation we receive translate(40px). The generated css for this mixin will look like this:

-webkit-transform: translate(40px);
transform: translate(40px);

Another way of reusing existing styles is to @include already existing rules. This one is not so common as mixins, because it does not have the ability to pass parameters to it, therefore it is not so flexible. You'll have an example on using this technique in the final demo for sass.

Hence, we have many ways to not repeat ourselves by defining mixins and including already existing rules. There is available online many collections of predefined mixins for sass, which provide many neat things. Most popular ones are bourbon and susy. You can read about them by clicking on the links I provided for you. Among the others, susy is a great alternative to bootstrap grids and it will prevent your html being polluted with unsemantic classes like col-md-4. You definitely should check them out.

Reimplementing demo

Now let me give you a full reimplementation of our kitties using sass, you can skim through code on codepen, it is very simple. Try to reveal all the new concepts we've covered here in the code for this demo.

See the Pen JopZRj by Andrei Glingeanu (@andreiglingeanu) on CodePen.


Conclusion

As a conclusion, all the preprocessors and sass specifically gives us a more flexible way to structure our styles, in comparison to pure css. Now all the nesting from HTML is expressed very naturally in our stylesheets. We also don't repeat ourselves by writing colors and scalar values, which is highly error prone and is very easily to commit a mistake by using sass variables. The ability to group multiple rules under one named mixin is also a beautiful and readable way of structuring css.

These little explanations should really get you started on using sass. If you really want more you can go ahead and read official guide for this preprocessor. You may find useful documentation, which gives many more ideas and insights on using and integrating sass on different platforms and development stacks.


Thursday, May 01, 2014

DRY-ing Out Your Sass Mixins

Sass Mixins

One of the most powerful features of the CSS preprocessor Sass is the mixin, an abstraction of a common pattern into a semantic and reusable chunk. Think of taking the styles for a button and, instead of needing to remember what all of the properties are, having a selector include the styles for the button instead. The button styles are maintained in a single place, making them easy to update and keep consistent.

Friday, November 01, 2013

Conditional Media Query Mixins

Conditional Media Query Mixins
Sass makes working with media queries pretty excellent. You can nest them right within other blocks of CSS, which puts the properties and values you are changing right next to each other. That creates an obvious connection between them, which is a much nicer authoring experience than trying to maintain those changes separated by tons of other code or in a different file. We've covered this before. It was new in Sass 3.2.

Tuesday, August 27, 2013

Responsive Images with Apropos

Responsive Images with Apropos

When Square's front-end engineering team was building our Japanese website, we decided early on that we needed to support a wide variety of devices, and that responsive design was the right way to do that. (Who wants to support a completely separate “mobile site”?) However, we weren't sure how we would serve every visitor the appropriate version of the gorgeous images shot by our talented photographers.

Wednesday, August 21, 2013

MetaFizzy Effect with Sass

MetaFizzy Effect with Sass
A couple of days ago, I saw this pen by Hugo Darby-Brown which intented to reproduce the MetaFizzy effect by David DeSandro in pure CSS with the help of Sass. Hugo did a great job and probably the most important one: digging into the original JavaScript to understand how to do it in Sass.

Tuesday, August 20, 2013

Advanced SASS list functions

Advanced SASS list functions

A couple of weeks ago, I wrote a small guide to understand Sass lists. I hope you've read it and learnt things from it!

Sunday, July 14, 2013

Getting started with CSS sourcemaps and in-browser Sass editing

CSS sourcemaps and in-browser Sass editing

CSS sourcemaps allow the browser to map CSS generated by a pre-processor, such as Sass, back to the original source file, including exactly which Sass mixin, placeholder or variable is responsible for a given line of CSS. And when you combine sourcemaps with Chrome's new in-browser local file editing (workspaces) and automatic stylesheet reloading, you end up with a very fast in-browser editing workflow.

Thursday, May 30, 2013

Sass Style Guide

Sass Style Guide
With more people than ever writing in Sass, it bears some consideration how we format it. CSS style guides are common, so perhaps we can extend those to cover choices unique to Sass.

Here are some ideas that I've been gravitating toward. Perhaps they are useful to you or help you formulate ideas of your own.

Tuesday, April 09, 2013

Modular CSS with Media Queries and Sass

Modular CSS with Media Queries and Sass

Most developers nowadays are recognizing that if you're developing large applications that have different views and states, it is best to take a modular or object-oriented approach to your CSS development.

Wednesday, April 03, 2013

Animations with SASS and @while

Animations with SASS and @while
A few months ago I was working on a looping Wi-Fi signal animation for Karma. Initially I wrote a bunch of SASS with a few mixins, until I discovered the @while directive which repeatedly outputs a given number nested blocks of styling. Resulting in 20 lines of SASS that generates as many animated signals as you could possibly want and automating the math as well.

Wednesday, April 03, 2013

All In The Family: Grouping CSS Selectors

Grouping CSS Selectors

Grouped selectors are one of the most efficient ways of reducing your CSS to tight, clean, easily maintained code, while bringing the benefit of imposing a stronger, more consistent design.

Saturday, November 10, 2012

Sass 3.2 makes authoring CSS3 as easy as it will be

Sass 3.2 makes authoring CSS3 as easy as it will be
In sass 3.2 there's a new kind of selector called a placeholder selector for use with @extend. Mixins and functions can now take any number of arguments and be defined in nested contexts. You can now pass a block of styles to a mixin for placement by the @content directive. Numbers will have now 5 digits of precision instead of 3. But that's not all. Read on for the details.

Thursday, October 11, 2012

Sass for Designers

Sass for Designers

Note: Throughout this post, when I say "Sass" I mean "Sass and Compass".

This is not a post saying...

If you're still building websites without Sass, you're doing it wrong.

This is a post saying:

If you haven't tried Sass/Compass, please do before dismissing it.

Thursday, October 11, 2012

Great resources for Compass and Sass

Compass and Sass

With front-end design getting more and more attention in web development these days, it's not a wonder that the two are beginning to meld into one. It is crucial to maintain consistency and efficiency in web projects and Sass/Compass tackles this head on. In geek speak, Sass is a meta language integrated with CSS that makes stylesheets more manageable and less repetitive. This simply means you can create CSS without copying and pasting and having to constantly search for previous style references.