Amy Westlake

< Back to blog

Running Gulp with Jekyll and browserSync

I always find myself re-writing gulpfiles for projects, I guess I just don’t add them to Git and then they get deleted so I wanted to document a complete gulpfile to stop this from happening. Also I think it will be helpful to document it in relation to Jekyll, which can sometimes be a bit of a pain to run.

Project Set Up

Every project I work on pretty much follows the same directory structure. It separates all the files by type, keeps everything organised and it’s pretty obvious where a file is going to be located if I have to search for it.

* assets
  ** CSS/
    *** Styles.scss
    *** Includes/
      **** _variables.scss
      **** _layouts.scss
    *** Libs/
      **** Slicknav.scss
  ** JS
    *** Libs/
      **** Jquery.min.js
    *** scripts.js
  ** Images/

For this Jekyll template I will be using a dev folder to store originals/files to be compiled and assets to store the compiled files ready to go into Jekylls site folder.

I want my Gulp file to initially do three things, compile SASS, minify the JS and compress any images. As an extra I want it running with browserSync and Jekyll.

To start we need to import all the plugs we will be using so in package.json, we list them all.

{
  "devDependencies": {
    "browser-sync": "^2.18.8",
    "del": "^2.2.2",
    "gulp": "^3.9.0",
    "gulp-autoprefixer": "^3.1.0",
    "gulp-cache": "^0.4.5",
    "gulp-clean-css": "^2.0.13",
    "gulp-concat": "^2.6.1",
    "gulp-connect": "^2.3.1",
    "gulp-imagemin": "^3.1.1",
    "gulp-notify": "^3.0.0",
    "gulp-plumber": "^1.0.1",
    "gulp-rename": "^1.2.2",
    "gulp-sass": "^2.1.1",
    "gulp-sourcemaps": "^2.4.1",
    "gulp-uglify": "^2.0.1",
    "gulp-util": "^3.0.7"
  }
}

After running npm install we need to reference them in the gulpfile.js.

const gulp = require('gulp'),
      sass = require('gulp-sass'),
      gutil = require('gulp-util'),
      plumber = require('gulp-plumber'),
      rename = require('gulp-rename'),
      minifyCSS = require('gulp-clean-css'),
      prefixer = require('gulp-autoprefixer'),
      connect = require('gulp-connect'),
      cp = require('child_process'),
      sourcemaps = require('gulp-sourcemaps'),
      concat = require('gulp-concat'),
      notify = require('gulp-notify'),
      cache = require('gulp-cache'),
      uglify = require('gulp-uglify'),
      imagemin = require('gulp-imagemin'),
      del = require('del'),
      browserSync = require('browser-sync').create();

Next we set some variables, that way we don’t have to repeat ourselves when asking for a source or destination. It also means if those directories ever change we can update in one place and not multiple times throughout the document. We are basically telling gulp where all our different files are located.

const base_path = './',
      src = base_path + '_dev/src',
      dist = base_path + 'assets',
      paths = {
          js: src + '/js/*.js',
          libs: src + '/js/libs/*js',
          scss: [ src +'/sass/*.scss',
                  src +'/sass/**/* .scss',
                  src +'/sass/**/**/*.scss'],
          images: [ src + '/images/**/*'],
          jekyll: ['index.html', '_posts/*', '_layouts/*', '_includes/*' , 'assets/*', 'assets/**/*']
      };

Styles

Our SASS files need to be compiled down into a single file, minified and use a sourcemap for easy development. Notice that we pass in the scss source variable, the plumber plugin looks for errors, if all is ok it then begins compiling. In order this function prefixes, generates a sourcemap, minifies, creates the sourcemap, saves the file to the assets/css folder, runs browserSync so we don’t have to refresh the browser then notifies us that the job is done.

gulp.task('styles', () => {
  return gulp.src(paths.scss)
    .pipe(plumber((error) => {
        gutil.log(gutil.colors.red(error.message));
        gulp.task('styles').emit('end');
    }))
    .pipe(sass())
    .pipe(prefixer('last 3 versions', 'ie 9'))
    .pipe(sourcemaps.init())
    .pipe(minifyCSS())
    .pipe(sourcemaps.write())
    .pipe(rename({dirname: dist + '/css'}))
    .pipe(gulp.dest('./'))
    .pipe(browserSync.stream())
    .pipe(notify({ message: 'Styles compiled' }));
});

Scripts

For JS libraries such as jQuery, Slicknav etc all we need it do to is concatenate them into a single file, minify it and save it out. Not much else is happening here. The custom scripts function is essentially the same, just with a different source folder to get the files from.

gulp.task('libs', () => {
  return gulp.src(paths.libs)
    .pipe(plumber((error) => {
        gutil.log(gutil.colors.red(error.message));
        gulp.task('libs').emit('end');
    }))
    .pipe(concat('libs.js'))
    .pipe(rename({suffix: '.min'}))
    .pipe(uglify())
    .pipe(rename({dirname: dist + '/js'}))
    .pipe(gulp.dest('./'))
    .pipe(notify({ message: 'JS libraries compiled' }));
});

Images

A quick and easy function that just takes an image, compresses it down and saves it to the assets/images folder.

gulp.task('images', function() {
  return gulp.src(paths.images)
    .pipe(cache(imagemin({ optimizationLevel: 5, progressive: true, interlaced: true })))
    .pipe(gulp.dest(dist + '/images'))
    .pipe(notify({ message: 'Images compiled' }));
});

Jekyll

Ok, so to get gulp running Jekyll we essentially have to ask it to run another process, the Jekyll Serve process. This is where the child_process plugin is needed. We are already running gulp and in order to get Jekyll running you would normally run Jekyll serve, here we are asking gulp to run the command instead.

gulp.task('build-jekyll', (code) => {
  return cp.spawn('jekyll', ['build', '--watch', '--incremental', '--drafts'])
    .on('error', (error) => gutil.log(gutil.colors.red(error.message)))
    .on('close', code)
})

browserSync

browserSync is one of those plugins that I always have trouble with. It never seems to want to work and to be honest I don’t have much of a problem refreshing the browser for each change anyway, it’s a habit that you get used to.

Here we need it run on Jekylls site folder, where the site files get generated. We set it to port 4000 which is where Jekyll runs.

gulp.task('serve', () => {
  browserSync.init({
    files: ['_site/**'],
    port: 4000,
    server: {
      baseDir: '_site'
    }
  })
});

Cleaning out files

This may seem a little unnecessary, but sometimes if you make changes the destination folder will end up containing files that are no longer being used. This just deletes all the files in the destination folder (here I’ve only done it on css and js, no need to bother with images) and then when the files are compiled you are left with only the files that you are using.

gulp.task('clean', function() {
    return del([dist + '/css', dist + '/js']);
});

Running and Watching

The watch task just keeps an eye on file changes, if a change is detected it run the relevant functions to compile as necessary.

At the bottom is our default gulp task. So now, we can just cd into our projects directory, run gulp and it boots up Jekyll, starts up browserSync (a new window will open automatically), compile all the existing assets and you are ready to get going with your development.

gulp.task('watch', () => {
  gulp.watch(paths.scss, ['styles']);
  gulp.watch(paths.libs, ['libs']);
  gulp.watch(paths.js, ['scripts']);
  gulp.watch(paths.images, ['images']);
  gulp.watch(paths.jekyll, ['build-jekyll']);
});

gulp.task('default', ['clean', 'build-jekyll', 'serve', 'styles', 'scripts', 'libs', 'styles', 'watch']);

< Back to blog