Codementor Events

Setting up a workflow with Gulp for fontend development

Published Sep 27, 2017Last updated Oct 01, 2018
Setting up a workflow with Gulp for fontend development

The Challenge

When you get the green light to a new web application project, how would you set up a workflow for your frontend development? Let's say LESS and ES6 are the essential technologies that you want to adopt, but writing ES6 for browsers is impossible unless you use Babel or something equivalent to translate ES6 to ES5 which they currently understand, too, writing pre-process CSS is tedious because it needs to be translated to 'actual' CSS before we can use it on our browsers. You need a quick and smooth development workflow like we used to do before these new technologies, without being interrupted by the translation and conversion process.

After many projects I have worked in the past, I finally narrowed it down to using Gulp, Babel and Browserify to help me for setting up the development workflow that I need.

Getting started

1. Directory structure

This is my basic directory structure:

-------------
dist/
    fonts/
fonts/
images/
javascripts/
stylesheets/
index.html
package.json
gulpfile.js
favicon.ico
-------------

dist/ is where all translated ES6 and LESS files are kept and it is where you should link them up in the link and script tags:

<link href="dist/bundle.min.css" rel="stylesheet">
<script src="dist/bundle.min.js"></script>

fonts/ is where all custom webfonts that I have generated from IcoMoon and Font Squerrel.

If you want to make your icons, website logos, etc into fonts, IcoMoon is great place to generate them for you.

We are going to use Gulp to help us to copy all fonts in fonts/ to dist/fonts/ for distribution.

So when the frontend is ready, these are the basic files and folders that you need to upload or to distribute:

-------------
index.html
favicon.ico
dist/
images/
-------------

2. package.json

This is the basic setup and dependencies I have in my package.json:

{
  "name": "example",
  "description": "packages for example",
  "author": "lau thiam kok",
  "license": "ISC",
  "dependencies": {
    "@babel/polyfill": "^7.0.0",
    "animate.css": "^3.7.0",
    "aos": "^2.3.3",
    "autosize": "^4.0.2",
    "axios": "^0.18.0",
    "es6-docready": "^1.0.0",
    "foundation-icon-fonts": "^0.1.1",
    "foundation-sites": "^6.5.0-rc.3",
    "jquery": "^3.3.1",
    "jquery-ui-bundle": "^1.12.1",
    "material-design-icons": "^3.0.1",
    "motion-ui": "^2.0.3",
    "swiper": "^4.4.1",
    "vue": "^2.5.17",
    "what-input": "^5.1.2"
  },
  "repository": {
    "type": "git",
    "url": "[git-url-of-your-project]"
  },
  "devDependencies": {
    "@babel/cli": "^7.1.2",
    "@babel/core": "^7.1.2",
    "@babel/plugin-transform-arrow-functions": "^7.0.0",
    "@babel/preset-env": "^7.1.0",
    "babelify": "^10.0.0",
    "browserify": "^16.2.3",
    "browserify-shim": "^3.8.14",
    "gulp": "^3.9.1",
    "gulp-batch": "^1.0.5",
    "gulp-clean-css": "^3.10.0",
    "gulp-clone": "^2.0.1",
    "gulp-concat": "^2.6.1",
    "gulp-concat-css": "^3.1.0",
    "gulp-cssimport": "^6.0.1",
    "gulp-foreach": "^0.1.0",
    "gulp-htmlmin": "^5.0.1",
    "gulp-less": "^4.0.1",
    "gulp-livereload": "^4.0.0",
    "gulp-sass": "^4.0.1",
    "gulp-sourcemaps": "^2.6.4",
    "gulp-uglify": "^3.0.1",
    "gulp-watch": "^5.0.1",
    "streamqueue": "^1.1.2",
    "vinyl-buffer": "^1.0.1",
    "vinyl-source-stream": "^2.0.0"
  },
  "browser": {
    "jquery": "./node_modules/jquery/dist/jquery.js"
  },
  "browserify-shim": {
    "jquery": "$"
  },
  "browserify": {
    "transform": [
      "browserify-shim"
    ]
  }
}

I have recently migrated from Bootsrap to Zurb Foundation because I find the latter has a better grid system.

3. gulpfile.js

And finally this is what I have in my gulpfile.js:

var gulp = require('gulp')
var sourcemaps = require('gulp-sourcemaps')
var livereload = require('gulp-livereload')
var watch = require('gulp-watch')
var batch = require('gulp-batch')

// JavaScript development.
var browserify = require('browserify')
var babelify = require('babelify')
var source = require('vinyl-source-stream')
var buffer = require('vinyl-buffer')
var uglify = require('gulp-uglify')

// Less compilation.
var less = require('gulp-less')

// CSS compilation.
var concat = require('gulp-concat')
var cleanCSS = require('gulp-clean-css')
var concatCss = require('gulp-concat-css') // optional

// HTML compilation.
var htmlmin = require('gulp-htmlmin')
var path = require('path')
var foreach = require('gulp-foreach')

// Task to compile js.
gulp.task('compile-js', function () {
  // app.js is your main JS file with all your module inclusions
  return browserify({
    extensions: ['.js'],
    entries:  ['./javascripts/app.js'],
    debug: true
  })
  .transform('babelify', {

    // https://babeljs.io/docs/en/env/
    presets: ['@babel/preset-env']
  })
  .bundle()
  .pipe(source('bundle.min.js'))
  .pipe(buffer())
  .pipe(sourcemaps.init())
  .pipe(uglify())
  .pipe(sourcemaps.write('./maps'))
  .pipe(gulp.dest('dist'))
  .pipe(livereload())
})

// Task to compile less.
gulp.task('compile-less', function () {
  return gulp.src([
    'stylesheets/master.less'
  ])
  .pipe(sourcemaps.init())
  .pipe(less())
  .pipe(sourcemaps.write('./maps'))
  .pipe(gulp.dest('stylesheets/css'))
})

// Task to minify css.
gulp.task('minify-css', function () {
  return gulp.src([
    'stylesheets/css/master.css'
  ])
  .pipe(sourcemaps.init())
  .pipe(cleanCSS({debug: true}))
  .pipe(concat('bundle.min.css'))
  .pipe(sourcemaps.write('./maps'))
  .pipe(gulp.dest('dist'))
  .pipe(livereload())
})

// Loop each html.
// https://www.npmjs.com/package/gulp-foreach
gulp.task('minify-html', function () {
  return gulp.src('*.html')
    .pipe(foreach(function(stream, file){
      // Get the filename.
      // https://github.com/mariusGundersen/gulp-flatMap/issues/4
      // https://nodejs.org/api/path.html#path_path_basename_p_ext
      var name = path.basename(file.path)
      return stream
        .pipe(htmlmin({
          collapseWhitespace: true,
          removeComments: true
        }))
        .pipe(concat('min.' + name))
    }))
    .pipe(gulp.dest(''))
})

// Task to copy fonts to dist.
gulp.task('copy-fonts', function() {
  return gulp.src([
    'fonts/*.{eot,svg,ttf,woff,woff2}',
    'fonts/**/*.{eot,svg,ttf,woff,woff2}',
    'node_modules/material-design-icons/iconfont/MaterialIcons-Regular.*',
    'node_modules/foundation-icon-fonts/foundation-icons.*',
  ])
  .pipe(gulp.dest('dist/fonts/'))
})

// Task to copy images to dist.
gulp.task('copy-images', function() {
  return gulp.src([
    'images/*.{jpg,png,gif,svg}',
    'images/**/*.{jpg,png,gif,svg}',
    'node_modules/jquery-ui-bundle/images/*',
  ])
  .pipe(gulp.dest('dist/images/'))
})

// Task to watch.
gulp.task('watch', function () {

  // Watch all js files recursively.
  watch([
      'javascripts/**',
      'javascripts/**/*.js'
    ], batch(function (events, done) {
      gulp.start('compile-js', done)
  }))

  // Watch all less files recursively.
  watch([
      'stylesheets/**',
      'stylesheets/**/*.less'
    ], batch(function (events, done) {
      gulp.start('compile-less', done)
  }))

  // Watch all css files recursively.
  watch([
      'stylesheets/**',
      'stylesheets/**/*.css'
    ], batch(function (events, done) {
      gulp.start('minify-css', done)
  }))

  // Watch all image files recursively.
  watch([
      'images/**',
      'images/**/*.{jpg,png,gif}'
    ], batch(function (events, done) {
      gulp.start('copy-images', done)
  }))

  // Watch all fonts files recursively.
  watch([
      'fonts/**',
      'fonts/**/*.{eot,svg,ttf,woff,woff2}'
    ], batch(function (events, done) {
      gulp.start('copy-fonts', done)
  }))
})

// Development:
// Task when running `gulp` from terminal.
gulp.task('default', ['watch'])

// Production:
// Task when running `gulp build` from terminal.
gulp.task('build', [
  'minify-css',
  'copy-fonts',
  'copy-images',
  'compile-js',
  'minify-html'
])

Gulp will watch any change in the LESS and ES6 code and translate them automatically for you. So you just need to refresh your browser to see the changes.

Using the workflow

To use this workflow, go to your project root directory and run it on your terminal then install the Node packages:

$ npm install

During LESS and ES6 development:

$ gulp watch

When you are ready for distribution:

$ gulp build

You can use the minified HTML index.min.html instead of index.html after the build.

Conclusion

As you can see it is quite a breeze to develop the frontend with Gulp when it is set up and you can work smoothly without worrying about the translation. You can download this sample project using this workflow in GitHub repository. Let me know what you think and what workflow you use for your projects. Any suggestions and errors, or any improvements to add to this workflow, please leave a comment below. Hope this workflow is helpful if you are struggling with the same problem I had.

Discover and read more posts from LAU TIAM KOK
get started