Gulp file for frontend project

The solution is not perfect. But it is suitable for developing a small frontend project written in coffeescript and less. Important note: the use of backend is not provided. Nodejs is only used for building assets (this solution is convenient, for example, if you plan to create a hybrid offline application for smartphones).

The key features of our project, and as a result, the gulp file:

1) Supports different environments (prod, dev, etc)

2) Compilation and minification of less, CoffeScript files, combining multiple into one (concatenation)

3) Support for tests (jasmine)

4) In the dev environment, watch is functioning (dynamically rebuilds assets when source files are changed)

The project has the following structure (unnecessary parts are not shown):

.

├── css

├── js

├── node_modules

├── public

│   ├── bower_components

│   ├── css

│   ├── img

│   └── js

└── spec

The sources are located in the css, js directories (those that are in the root). The public directory contains everything that is ready for display: index.html file, compiled assets. The spec directory is for jasmine tests.

More details on the gulpfile structure:

Used modules list:

var gulp = require('gulp'),
    less = require('gulp-less'),
    coffee = require('gulp-coffee'),
    concat = require('gulp-concat'),
    watch = require('gulp-watch'),
    batch = require('gulp-batch'),
    fs = require('fs'),
    argv = require('yargs').argv
;

First, let's define the environment:

// Environment detecting
var env = argv.env || 'prod',
    envFile = '.env'
;

if (!argv.env && fs.existsSync(envFile)) {
    env = fs.readFileSync(envFile, 'utf8');
}

As you can see, the environment is determined either by the argument of the gulp command or by the content of the envFile (my envFile - .env file in the project root).

Let's add the .less files compilation:

gulp.task('less', function () {
    gulp.src('./css/**/*.less')
        .pipe(less())
        .pipe(concat('style.css'))
        .pipe(gulp.dest('./public/css/'))
    ;
});

Note the mask: this way, all files in the ./css directory will be recursively compiled.

Let's add the .coffee file compilation. Here it's a bit more complicated. The thing is, it's convenient to separate the sources (let's call them "libraries") and the application launch script. This is necessary, for example, for running tests.

gulp.task('coffee-lib', function () {
    gulp.src(['./js/**/*.coffee', '!./js/app.coffee'])
        .pipe(coffee())
        .pipe(concat('lib.js'))
        .pipe(gulp.dest('./public/js/'))
    ;
});

gulp.task('coffee-app', function () {
    gulp.src('./js/app.coffee')
        .pipe(coffee())
        .pipe(concat('app.js'))
        .pipe(gulp.dest('./public/js/'))
    ;
});

As you can see, the ./js/app.coffee file is excluded in the coffee-lib task.

Let's add a task for tests:

gulp.task('coffee-spec-common', function () {
    gulp.src('./spec/common.coffee')
        .pipe(coffee())
        .pipe(concat('spec-common.js'))
        .pipe(gulp.dest('./public/js/'))
    ;
});

gulp.task('coffee-spec', function () {
    gulp.src(['./spec/**/*.coffee', '!./spec/common.coffee'])
        .pipe(coffee())
        .pipe(concat('spec.js'))
        .pipe(gulp.dest('./public/js/'))
    ;
});

coffee-spec-common task compiles common utilities for tests (for example, I have methods there that check the equality of two-dimensional arrays and others) separately to ensure they are included before the tests.

Let's add watch and batch for development convenience:

gulp.task('watch', function() {
    watch(['./js/**/*.coffee'], batch(function(events, cb) {
        gulp.start('default', cb);
    }));

    watch(['./spec/**/*.coffee'], batch(function(events, cb) {
        gulp.start('default', cb);
    }));

    watch(['./css/**/*.less'], batch(function(events, cb) {
        gulp.start('default', cb);
    }));
});

(Batch adds a delay before recompilation. It eliminates a performance issue when switching branches in git).

Let's add watch in dev mode and the main task:

var tasks = ['coffee-lib', 'coffee-app', 'less'];

if (env === 'dev') {
    tasks.push('watch');
    tasks.push('coffee-spec-common');
    tasks.push('coffee-spec');
}

gulp.task('default', tasks);

You can see a full example here