15 févr. 2014

Keep your Gulpfile manageable: the Gulp plugins you need to know

CoffeeScript as your Gulpfile

Though being less in the hype today, CoffeeScript shortens the need of writing boilerplate codes (as already detailed in this post A gulp of coffee: your gulpfile in coffeescript). All you need to do is to follow these simple steps:
  1. Start by installing CoffeeScript globally:
    npm -g install coffee-script
  2. Create a regular Gulpfile.js that bootstraps Gulp into using CoffeeScript:
    require('coffee-script/register');
    // This bootstraps your Gulp's main file
    require('./Gulpfile.coffee');
  3. Now, you simply edit your Gulpfile.coffee and forget about most of the semicolon, accolades, parenthesis, ...

Load plugins, automatic loading of your plugins

Like MatchDep or grunt-load-tasks do it for NodeJS and Grunt, gulp-load-plugins leverages your package.json and automatically loads all the plugins in Gulp. Here are the steps to follow:
  1. Start by creating a regular package.json for your project:
    npm init
  2. Add locally all your required plugins and save their installation as a development dependency. Here is an example for gulp-clean:
    npm install --save-dev gulp-clean
  3. At the very beginning of your Gulpfile.coffee, load Gulp and every plugins in two lines of code:
    gulp = require 'gulp'
    gp = do require 'gulp-load-plugins'  # Load all gulp plugins
  4. Now all your plugins are loaded and accessible from the gp object. Their name matches the plugins name in camel case. Putting it more simply, for the plugin gulp-clean, you use it as gp.clean, for the plugin gulp-ruby-sass, you use is as gp.rubySass.

Plumber, avoid restarting Gulp when transpiling fails

When you start your watch and transpile tasks for the first time, it happens that syntax errors abruptly ends Gulp. It forces you to go the old way, fixing the bugs, re-launching Gulp, fixing the bugs, re-launching Gulp, ... tedious, to say the least.
gulp-plumber avoids the stream to end upon error. Thus, whenever you need a transpilation, you just add gulp-plumber to your pipes. Here is a simple example that shows up how to use it.
gulp.task 'css', ->
  gulp.src 'app/css/index.sass'
    .pipe gp.watch()
    .pipe gp.plumber()    # Add Plumber just before the transpilation
    .pipe gp.rubySass()
    .pipe gulp.dest 'www/css'
Installing is made easy thanks to the automatic loading of plugins as described in the previous paragraph:
npm install --save-dev gulp-plumber

Connect, start a livereload server, open your file and a create a static server in one plugin

This plugin gulp-connect is incredibly powerful: it combines a connect server with a tiny-lr one and it opens your transpiled files. Pure voodoo style. Here is short example of its basic usage:
path  = require 'path'
# Start a webserver for static files
gulp.task 'webserver', gp.connect.server
  root: path.join __dirname, 'www'
  port: 9000
  livereload: true
  open: browser: 'Google Chrome'
Just like before, the installation is easy as pie:
npm install --save-dev gulp-connect
Now, on each watch task requiring a livereload, just add a gp.connect.reload() call to their task.
Note : BrowserSync is also a very good alternative.

Autoprefixer, forget about vendor's prefixes

Wether you use pure CSS, Less, Stylus or Sass and its compagnon Compass, you have to take care of vendor prefixes depending on the public your website or hybrid app are targeting. Doing it manually is a pure madness. But using some libraries assistance may also ends up in a tedious work, piling up a bunch of libraries that you need to add or remove depending on public adoption of next generation of operating systems and browsers.
Autoprefixer does this automatically for you. It leverages the usage statistics Caniuse (by the way, an incredible and so useful website) and adds to your CSS just the prefix that it requires. A life time saver. Using it in Gulp is incredibly easy. Here is a simple example that takes Sass as its input and produces the CSS for 99% of browsers:
# Create CSS from SASS
gulp.task 'css', ->
  gulp.src 'app/css/index.sass'
    .pipe gp.watch()
    .pipe gp.plumber()
    .pipe gp.rubySass()
    .pipe gp.autoprefixer "> 1%"  # Set Autoprefixer for 99%
    .pipe gulp.dest 'www/css'

A complete sample

To complete this article, here is one of the Gulpfile.coffee that I've used on a production website (a very basic SPA):
gulp  = require 'gulp'
# Load all gulp plugins
gp    = do require 'gulp-load-plugins'
path  = require 'path'

# Start a webserver for static files
gulp.task 'webserver', gp.connect.server
  root: path.join __dirname, 'www'
  port: 9000
  livereload: true
  open: browser: 'Google Chrome'

# Create CSS from SASS
gulp.task 'css', ->
  gulp.src 'app/css/index.sass'
    .pipe gp.watch()
    .pipe gp.plumber()
    .pipe gp.rubySass()
    .pipe gp.autoprefixer "> 1%"
    .pipe gp.cssmin keepSpecialComments: 0
    .pipe gulp.dest 'www/css'
    .pipe gp.connect.reload()

# Create HTML from Jade
gulp.task 'html', ->
  gulp.src 'app/index.jade'
    .pipe gp.watch()
    .pipe gp.plumber()
    .pipe gp.jade()
    .pipe gulp.dest 'www'
    .pipe gp.connect.reload()

# Copy font files
gulp.task 'copy_fonts', ->
  gulp.src './bower_components/font-awesome/fonts/*'
    .pipe gulp.dest 'www/fonts'

# Copy vendor JS files, concatenate them and uglify them
gulp.task 'copy_js', ->
  gulp.src [
    'bower_components/better-dom/dist/better-dom.js'
    'bower_components/better-details-polyfill/dist/better-details-polyfill.js']
    .pipe gp.concat 'better-dom-and-plugin.js'
    .pipe gp.uglify()
    .pipe gulp.dest 'www/js'

# Copy vendor CSS files and minifies it
gulp.task 'copy_css', ->
  gulp.src [
    'bower_components/better-details-polyfill/dist/better-details-polyfill.css']
    .pipe gp.cssmin keepSpecialComments: 0
    .pipe gulp.dest 'www/css'

# Clean produced files
gulp.task 'clean', ->
  gulp.src ['www', 'tmp']
    .pipe gp.clean()

gulp.task 'default', [
  'copy_fonts', 'copy_js', 'copy_css'
  'css', 'html', 'webserver']
And here are the relevant part of its package.json:
{
  "name": "Blablabla",
  "version": "0.0.0",
  "description": "",
  "main": "Gulpfile.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Me",
  "license": "MIT",
  "devDependencies": {
    "gulp-cssmin": "~0.1.0",
    "gulp-ruby-sass": "~0.3.0",
    "gulp-util": "~2.2.14",
    "gulp-autoprefixer": "0.0.6",
    "gulp": "~3.5.2",
    "gulp-jade": "~0.4.1",
    "gulp-watch": "~0.5.0",
    "gulp-sass": "~0.6.0",
    "gulp-plumber": "~0.5.6",
    "gulp-clean": "~0.2.4",
    "gulp-load-plugins": "~0.3.0",
    "gulp-concat": "~2.1.7",
    "gulp-uglify": "~0.2.1",
    "gulp-connect": "~0.3.1"
  }
}