Gulp.js

The streaming build system

Table of contents

  • What is Gulp?
  • Grunt VS Gulp
  • The Gulp API
  • What's next?
  • Hands On Gulp

What is Gulp?

Gulp is a build framework, much like Grunt, Assetic, or Capistrano, but whose particularity is to work by streams, or pipes.

This way, it privilegies code over configuration

making building tasks fairly simple and easily maintainable.

Example


gulp.task('task', function(){
    return gulp.src('**/*.file', {cwd: 'app/'})
            .pipe(coffee())
            .pipe(concat())
            .pipe(uglify())
            .pipe(gulp.dest('dist/'))
});
  • Easy to configure
  • Plain Old Node Modules
  • Chainable syntax
  • Really fast

What is a stream?

Pipes

Think of a stream as a flow of data flowing into pipes.

Data comes raw into the pipe, then passes a serial of processors one after another, to finally return the desired result.

Think of a build system in your head

Not this...

This is what comes into mind:

1) Grunt plugins

  • Grunt relies too much on plugins
    • Plugin to clean, to open a browser...
  • Not compatible with node modules
    • Everything needs to be under initConfig so...
  • Want to run a simple command? (ex: bower)
    • ​Well you can't!

2) Configuration all over the place

grunt.initConfig({
    htmlmin: {
      options: {
        collapseWhitespace: true,
        removeComments: false
      },
      docs: {
        files: [{
          expand: true,
          cwd: '<%= yo.pages %>',
          src: ['*.html'],//, 'views/{,*/}*.html'],
          dest: '<%= yo.pages %>'
        }]
      }
    },

    copy: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          dest: '<%= yo.dist %>',
          src: '{,*/}*.css'
        }]
      },
      docs: {
        files: [{
          expand: true,
          cwd: '<%= yo.docs %>/',
          dest: '<%= yo.pages %>',
          src: [
            'images/*',
            '1.0/**/*'
          ]
        }]
      }
    }
});
  • Configuration for everything
  • A lot of options, needing people to look at the npm documentation almost every time
  • Subtasks need to be defined inside the configuration object
    • Ex: concat:dist, concat:dev, concat:docs ...
  • Configuration values are primitive types (strings, numbers)... so we need the ugly "<%= yo.src %>"...

2) Configuration all over the place

3) Temporary folders

3) Temporary folders

  • Grunt is a mess to understand
    • Should we compile to .tmp or dist?
    • So we first copy to .tmp then dist?
  • Much slower
    • Grunt needs to check the filesystem for each task
    • First write into tmp, then write into dist

So is Grunt so bad?

  • Grunt has a much larger community than Gulp (for now)
  • Gulp is still in its early phases, meaning there could be bugs
  • Grunt is less error-prone: If something fails, you can immediately know where it is.

 

There are people who prefer configuration over code (especially non-Node developers).

Grunt advantages

The Gulp API

gulp.task

It all starts with a task

gulp.task('compass', ['clean'], function(){
    // Process data inside...
});
  • Registers a task "compass"
  • Has a dependency, "clean", that will run before this task.
  • Gulp tasks run with maximal concurrency

gulp.src

Drink it up!

gulp.task('compass', ['clean'], function(){
    return gulp.src('**/*.scss', {cwd: 'app/styles'})
        // what to do with this stream
});
  • The first argument is a glob (pattern with *) of the files to drink
  • The second argument is a list of options (see node-glob)
  • Returns a stream that can be passed through pipes

.pipe

This is not a pipe

gulp.task('compass', ['clean'], function(){
    return gulp.src('**/*.scss', {cwd: 'app/styles'})
        .pipe(compass())
        .pipe(autoprefixer('last 1 update'))
        .pipe(concat('style.css'))
        // Now we need to "spit out" the result
});
  • The pipe takes a function that will process the current stream and return a new stream
  • Can be chained (which is the whole point)
  • Easy to understand what is going on

.pipe

gulp.dest

Spill the beans!

gulp.task('compass', ['clean'], function(){
    return gulp.src('**/*.scss', {cwd: 'app/styles'})
        .pipe(compass())
        .pipe(autoprefixer('last 1 update'))
        .pipe(concat('style.css'))
        .pipe(gulp.dest('dist'));
});
  • Write out the processed stream into the destination
  • Returns a stream... meaning you can still chain!

gulp.dest

gulp.task('compass', ['clean'], function(){
    return gulp.src('**/*.scss', {cwd: 'app/styles'})
        .pipe(compass())
        .pipe(autoprefixer('last 1 update'))
        .pipe(concat('style.css'))
        .pipe(gulp.dest('dist'))
        // Immediately after writing style.css, create a minified version
        .pipe(rename(function (path) { path.extname = '.min.css'; }))
        // uglify
        .pipe(csso())
        .pipe(gulp.dest('dist'));
});

gulp.watch

The watchers

gulp.task('watch', function(){
    gulp.watch('**/*.scss', ['clean', 'compass']).pipe(connect.reload());
    gulp.watch('**/*.js', ['clean', 'compile']).pipe(connect.reload());
});
  • Yes, gulp.watch also return streams, meaning that you can also chain them
  • Incorporated into the gulp-core

That's it!

Now everyone can build up gulp tasks without reading pages of documentation!

Example gulpfile

var gulp = require('gulp');
var pkg = require('./package.json');
var concat = require('gulp-concat');
var minify = require('gulp-minify');
var jshint = require('gulp-jshint');
var spawn = require('child_process').spawn;

var scriptFiles = './src/**/*.js';

gulp.task('compile', function(){
  // concat all scripts, minify, and output
  gulp.src(scriptFiles)
    .pipe(concat({fileName: pkg.name+".js"})
    .pipe(minify())
    .pipe(gulp.dest('./dist/'));
});

gulp.task('test', function(){
  // lint our scripts
  gulp.src(scriptFiles).pipe(jshint());
  
  // run our tests
  spawn('npm', ['test'], {stdio: 'inherit'});
});
    
gulp.task('default', function(){
  gulp.run('test', 'compile');
  gulp.watch(scriptFiles, function(){
    gulp.run('test', 'compile');
  });
});

Rules of thumb

  1. All gulp functions and requires are set at the top
  2. Sequential tasks (build, serve...) are set at the bottom
  3. Declare all sources at the top so you can use something like "src.scripts" instead of "**/*.js"
  4. LAZY LOADING!!!
    1. Since gulp is a simple node module, put each task in its own file and use module.exports!
    2. gulpfile.js will require the necessary tasks.
  5. Don't forget to listen to error events to prevent the watchers to stop!

Useful gulp plugins

  1. gulp-size: show the size of the compiled file
  2. gulp-sourcemaps: generate sourcemaps (annotated comments useful to debug inside a concatenated file)
  3. gulp-ngtemplate: put all templates inside the $templateCache
  4. gulp-autoprefixer: automatically generate css prefixes (moz-, webkit-...) when necessary
  5. gulp-changed: run the task only when files change
  6. wiredep: automatically inserts bower_components inside html
  7. gulp-plumber: prevent errors to stop gulp

 

See http://gulpjs.com/plugins/

What's next?

Gulp 4.0

  • Parallel and Sequential Tasks
  • Better task management
  • Implicit error management (no need for plumber!)
  • Import and export recipes...

Available Plugins

  • Sublime Text/TextMate plugin
  • PHPStorm 8/Webstorm 9
  • Oh My Zsh plugin
  • Chrome Dev Tools extension
  • Yeoman generators
  • Even in Google Web Starter Kit!

 

Sheelot?

Gulp.js

By Elior Boukhobza

Gulp.js

Gulp.js, the streaming build framework

  • 1,403