3
\$\begingroup\$

I'm trying to write gulpfile.js, which will follow modern best practices for optimization. This project includes jekyll, SASS and some image optimization tasks. I'm pretty new to JavaScript and Gulp, so I'd love to get some feedback on this code.

You can also check the Github repo.

'use strict';

var gulp            = require('gulp'),
    plumber         = require('gulp-plumber'),
    gutil           = require('gulp-util'),
    notify          = require('gulp-notify'),
    size            = require('gulp-size'),
    rename          = require('gulp-rename'),
    sass            = require('gulp-sass'),
    autoprefixer    = require('gulp-autoprefixer'),
    cmq             = require('gulp-combine-media-queries'),
    minifyCSS       = require('gulp-minify-css'),
    jshint          = require('gulp-jshint'),
    concat          = require('gulp-concat'),
    uglify          = require('gulp-uglify'),
    psi             = require('psi'),
    ngrok           = require('ngrok'),
    cp              = require('child_process'),
    browserSync     = require('browser-sync'),
    imageMin        = require('gulp-imagemin'),
    cwebp           = require('gulp-cwebp'),
    imageResize     = require('gulp-image-resize'),
    cache           = require('gulp-cache'),
    cached          = require('gulp-cached'),
    changed         = require('gulp-changed'),
    newer           = require('gulp-newer'),
    parallel        = require('concurrent-transform'),
    os              = require('os-utils'),
    minifyHTML      = require('gulp-minify-html'),
    clean           = require('gulp-clean'),
    url             = 'http://1ec934f4.ngrok.com/',
    reload          = browserSync.reload;

var onError         = function(err) {
    gutil.beep();
    gutil.log(gutil.colors.green(err + '\n'));
};

var messages = {
    jekyllBuild: '<span style="color: grey">Running:</span> $ jekyll build'
};

/*==========  Paths  ==========*/

var paths = {
    styles: {
        src:            'assets/sass/main.scss',
        dest:           'build/css',
        destProd:       'html/build/css',
        watch:          'assets/sass/**/*.scss',
        style:          'build/css/main.css',
        styleProd:      'html/build/css/main.css',
        styleMin:       'build/css/main.min.css',
        styleMinProd:   'html/build/css/main.min.css'
    },
    scripts: {
        src:            'assets/js/**/*.js',
        dest:           'build/js',
        destProd:       'html/build/js',
        destVen:        'assets/js/vendor',
        script:         'build/js/main.js',
        scriptProd:     'html/build/js/main.js',
        scriptMin:      'build/js/main.min.js',
        scriptMinProd:  'html/build/js/main.min.js',
        bundleMain:     'build/js/main.bundle.js',
        bundleMainMin:  'build/js/main.bundle.min.js',
        watch:          ['assets/js/**/*.js', '!/assets/js/vendor/**/*.js']
    },
    jshint: {
        src: [
                        'assets/js/**/*.js',
                        '!assets/js/vendor/**/*.js'
        ]
    },
    bundles: {
        main: [
                        'assets/js/main.js',
                        'assets/js/vendor/**/*.js',
                        '!assets/js/vendor/bootstrap-sprockets.js',
                        '!assets/js/vendor/bootstrap.js',
        ]
    },
    images: {
        src:            'assets/img/**/*.{jpg, png, svg, gif, webp}',
        dest:           'build/img',
        destOpt:        'build/img/opt',
        destProd:       'html/build/img',
        watch:          'assets/img/**/*.{jpg, png, svg, gif, webp}',
        watchOpt:       'build/img/opt/*.{jpg, png, svg, gif, webp}',
        watchDest:      'build/img/*.{jpg, png, svg, gif, webp}',
        watchProd:      'html/build/img/**/*.{jpg, png, svg, gif, webp}'
    },
    html: {
        dest:           'html',
        watch:          ['index.html', '_layouts/*.html', '_includes/**/*', '_posts/**/*'],
        watchProd:      'html/**/*.html'
    },
    copy: {
        styles:         'bower_components/bootstrap-sass-official/assets/stylesheets/**/*.scss',
        scripts:        'bower_components/bootstrap-sass-official/assets/javascripts/**/*.js'
    },
    clean: {
        img:            'build/img',
        imgProd:        'html/build/img',
        styles:         'build/css',
        stylesProd:     'html/build/css',
        scripts:        'build/js',
        scriptsProd:    'html/build/js',
        build:          'build',
        buildProd:      'html/build',
        prod:           'html'
    }
};



/*==================================
=            Gulp Tasks            =
==================================*/



gulp.task('browser-sync', function() {
    browserSync({server: {baseDir: paths.html.dest}});
});

gulp.task('sass', function () {
    return gulp.src(paths.styles.watch)
        .pipe(plumber({errorHandler: onError}))
        .pipe(sass({outputStyles: 'nested'}))
        .pipe(autoprefixer(['last 15 versions', '> 1%', 'ie 8', 'ie 7'], {cascade: true}))
        .pipe(gulp.dest(paths.styles.dest))
        .pipe(gulp.dest(paths.styles.destProd))
        .pipe(size({showFiles: true}))
        .pipe(notify('sass is done.\n'));
});

gulp.task('sass-min', ['sass'], function () {
    return gulp.src(paths.styles.style)
        .pipe(plumber({errorHandler: onError}))
        .pipe(cmq({log: true}))
        .pipe(minifyCSS())
        // .pipe(zopfli())
        // .pipe(zip('main.css.zip'))
        .pipe(rename({suffix: '.min'}))
        .pipe(gulp.dest(paths.styles.dest))
        .pipe(gulp.dest(paths.styles.destProd))
        .pipe(size({showFiles: true}))
        .pipe(notify('Sass-min is done.\n'));
});

gulp.task('sass-reload', ['sass-min'], function () {
    return gulp.src(paths.styles.styleMin)
        .pipe(reload({stream: true}));
});

gulp.task('jshint-gulp', function () {
    return gulp.src('gulpfile.js')
        .pipe(cache(jshint('.jshintrc')))
        .pipe(jshint.reporter('jshint-stylish'))
        .pipe(notify('jshint-gulp is done.\n'));
});

gulp.task('jshint', ['jshint-gulp'], function () {
    return gulp.src(paths.scripts.script)
        .pipe(cache(jshint('.jshintrc')))
        .pipe(jshint.reporter('jshint-stylish'))
        .pipe(notify('jshint-gulp is done.\n'));
});

gulp.task('concat', ['jshint'], function () {
    return gulp.src(paths.bundles.main)
        .pipe(concat('main.bundle.js'))
        .pipe(gulp.dest(paths.scripts.dest))
        .pipe(gulp.dest(paths.scripts.destProd))
        .pipe(size({showFiles: true}));
});

gulp.task('js', ['concat'], function () {
    return gulp.src(paths.scripts.bundleMain)
        .pipe(uglify())
        .pipe(rename({suffix: '.min'}))
        .pipe(gulp.dest(paths.scripts.dest))
        .pipe(gulp.dest(paths.scripts.destProd))
        .pipe(notify('js is done.\n'));
});

gulp.task('js-reload', ['js'], function () {
    return gulp.src(paths.scripts.bundleMainMin)
        .pipe(plumber({errorHandler: onError}))
        .pipe(reload({stream:true}));
});

gulp.task('image-min', function () {
    return gulp.src(paths.images.src)
        .pipe(plumber({errorHandler: onError}))
        .pipe(cached(imageMin({optimizationLevel: 3, progressive: true, interlaced: true})))
        .pipe(gulp.dest(paths.images.destOpt))
        .pipe(size({showFiles: true}))
        .pipe(notify('image-min is done.\n'));
});

gulp.task('image-resize-sm', ['image-min'], function () {
    return  gulp.src(paths.images.watchOpt)
        .pipe(plumber({errorHandler: onError}))
        .pipe(changed(paths.images.dest))
        .pipe(parallel(imageResize({
            width   : 400,
            height  : 300,
            crop    : true,
            upscale : false
        }), os.cpuUsage(function(v) { console.log( 'CPU Usage (%): ' + v ); })))
        .pipe(rename({suffix: '-sm'}))
        .pipe(gulp.dest(paths.images.dest))
        .pipe(size({showFiles: true}))
        .pipe(gulp.dest(paths.images.destProd));
    });

gulp.task('image-resize-md', ['image-resize-sm'], function () {
    return  gulp.src(paths.images.watchOpt)
        .pipe(plumber({errorHandler: onError}))
        .pipe(changed(paths.images.dest))
        .pipe(parallel(imageResize({
            width : 800,
            height : 600,
            crop : true,
            upscale : false
        }), os.cpuUsage(function(v) { console.log( 'CPU Usage (%): ' + v ); })))
        .pipe(rename({suffix: '-md'}))
        .pipe(gulp.dest(paths.images.dest))
        .pipe(size({showFiles: true}))
        .pipe(gulp.dest(paths.images.destProd));
});

gulp.task('image-resize-lg', ['image-resize-md'], function () {
    return  gulp.src(paths.images.watchOpt)
        .pipe(plumber({errorHandler: onError}))
        .pipe(changed(paths.images.dest))
        .pipe(parallel(imageResize({
            width : 1200,
            height : 900,
            crop : true,
            upscale : false
        }), os.cpuUsage(function(v) { console.log( 'CPU Usage (%): ' + v ); })))
        .pipe(rename({suffix: '-lg'}))
        .pipe(size({showFiles: true}))
        .pipe(gulp.dest(paths.images.dest))
        .pipe(gulp.dest(paths.images.destProd));
});

gulp.task('cwebp', ['image-resize-lg'], function () {
    return gulp.src(paths.images.watchProd)
        .pipe(plumber({errorHandler: onError}))
        .pipe(changed(paths.images.watchDest))
        .pipe(parallel(cwebp()), os.cpuUsage(function(v) { console.log( 'CPU Usage (%): ' + v ); }))
        .pipe(gulp.dest(paths.images.dest))
        .pipe(gulp.dest(paths.images.destProd))
        .pipe(notify('cwebp is done.\n'));
});

gulp.task('img-reload', ['cwebp'], function () {
    return gulp.src(paths.images.destProd)
        .pipe(plumber({errorHandler: onError}))
        .pipe(reload({stream:true}))
        .pipe(notify('img-reload is done.\n'));
});

gulp.task('jekyll-build', function (done) {
    browserSync.notify(messages.jekyllBuild);
    return cp.spawn('jekyll', ['build'], {stdio: 'inherit'})
        .on('close', done);
});

gulp.task('jekyll', ['jekyll-build'], function() {
    return gulp.src(paths.html.watchProd)
        .pipe(minifyHTML())
        .pipe(gulp.dest(paths.html.dest));
});

gulp.task('jekyll-rebuild', ['jekyll'], function () {
    reload();
});

gulp.task('copy-css', function() {
    return gulp.src(paths.copy.styles)
        .pipe(gulp.dest('assets/scss/vendor'));
});

gulp.task('copy-js', function() {
    return gulp.src(paths.copy.scripts)
        .pipe(gulp.dest(paths.scripts.destVen));
});

gulp.task('clear-cache', function(done) {
    return cache(cache.caches = {});
});

gulp.task('clean', ['clear-cache'], function() {
  return gulp.src(paths.html.dest, {read: false})
    .pipe(clean());
});

gulp.task('ngrok', function () {
    ngrok.connect({
        authtoken: 'aGJcNEqh838HDfvyheIe', //2BUMGp1fJDzPal7FvxN2
        //httpauth: 'login:password',
        port: 3000
      }, function(err, url) {
        if (err !== null) {
          console.log( err );
        }
    });
});

gulp.task('pagespeed', ['ngrok'], function () {
    return psi({
        nokey: 'true', // or use key: ‘YOUR_API_KEY’
        url: url,
        strategy: 'mobile'
    });
});

gulp.task('watch', ['sass-min', 'js', 'browser-sync'], function () {
    gulp.watch(paths.styles.watch, ['sass-reload']);
    gulp.watch(paths.scripts.watch, ['js-reload']);
    gulp.watch(paths.images.watch, ['img-reload']);
    gulp.watch(paths.html.watch, ['jekyll-rebuild']);
});

In addition to that I also have some questions, but I am mainly concerned on the code quality.

  1. How can I properly use os-utils to output CPU and memory usage in % used over the single task?
  2. Does my onError function works properly? I'm not quite clear with how to use beep and gutil.
  3. How can I properly cache (or change, remeber, cached or whatever other caching plugin) images for gulp-resize and gulp-cwebp tasks?
\$\endgroup\$
6
  • \$\begingroup\$ This Gulpfile.js is monolithic. \$\endgroup\$
    – Brendan
    Oct 28, 2014 at 5:29
  • \$\begingroup\$ What do you mean ? \$\endgroup\$
    – iamskok
    Oct 28, 2014 at 5:53
  • \$\begingroup\$ I was in the middle of writing up a review, but I thought it was a great file and ended up not officially leaving an answer. My only advice would be to separate and restructure the file into a build/ directory with different tasks, since it is 330+ lines long. \$\endgroup\$
    – Brendan
    Oct 28, 2014 at 6:13
  • \$\begingroup\$ I probably didn't understand your idea about separating /build directory with different tasks, but I suppose that I don't need any specific tasks for /build directory, because it's always get overwritten by 'jekyll-build' task... Please correct me, if I miss something. Thanks for your review ✌️ \$\endgroup\$
    – iamskok
    Oct 28, 2014 at 6:23
  • \$\begingroup\$ If you'd like, I can leave an answer that provides my advice for the build/ directory instead of a comment - but it won't have much else. \$\endgroup\$
    – Brendan
    Oct 28, 2014 at 6:27

1 Answer 1

3
\$\begingroup\$

My only opinion on your mostly well written Gulpfile is to consider restructuring your 330+ line Gulpfile into multiple files, separated by grouped tasks.

During an overview of your file, the tasks seemed to be loosely categorized into three groups:

  • image modifications
  • copying / Jekyll
  • JavaScript and Sass

As someone who doesn't like large files of code, I would suggest a build/ directory and a main Gulpfile.js to run your builds. Something along the following tree:

/Gulpfile.js
/build/images.js
/build/statics.js
/build/js-sass.js

This StackOverflow question handles the "how to" very nicely. Besides that tip (and some indentation errors), I think the Gulpfile looks great.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.