My Gulp file setup for static sites development

All links in this post open in a new tab

Hello, in this post I will be sharing my Gulp file and showing how I use it to run repetitive tasks like refreshing the browser, compressing images, and processing css files such as sass.

Gulp has been the only build tool I have used consistently since I learned about it a few years ago. I have played with different configurations over time and I have used it for even single tasks, like refreshing the browser when I edit a file, to processing, concatenating, and minifying multiple files in one fell swoop.

So, I want to share with you the current file I use to do my processing of local files before I serve those up to a production server. This file has been a slow progressive build due in part to experimenting and in part by learning from other resources on the web. 

You can find this Gulp file on my Github repo if you want to download it and start using it right away.

Hopefully this gets you started using Gulp, or helps you build upon your existing Gulp file and get it closer to perfection. 

Ok, let’s get started.

The Directory Structure

First of all, the directory structure can be anything you want. When working on your computer the only thing to keep in mind is that all your files and directories should be under one main directory. These files and directories will mirror your websites file structure on the web server.

Below is the structure I use for my own projects, which I borrowed from one of Ray Villalobos courses on Lynda.com. I share this so that the file paths I show on the Gulp file will make a bit more sense, but again, this can be anything you want. Just make sure to update the file paths in the Gulp file with your own paths.

projectName
- builds
 -- development
 -- production
- components
 -- sass
 -- scripts
...

The Gulp File

If you are totally new to Gulp, then you will need to install it on your machine. Just head over to the Gulp’s ‘get started’ page for the latest installation requirements. 

This file uses Gulp 4 syntax. So if you have used Gulp but you are still using version 3, please check out Jessica Chan’s post on using Gulp 4 in your workflow which does a way better job at introducing Gulp and includes a section on how to migrate from version 3 to version 4 syntax. 

With this file, I will be performing 4 main tasks:

  1. Compile Sass to css
  2. Concatenate and minify Js files
  3. Compress and optimize images for production, and last but not least
  4. Refresh the browser any time I edit a file. 

This Gulp file also has a nice little cache hack to make sure css styles and Js scripts are as fresh as possible in the browser. I have to thank Jessica Chan for sharing her Gulp file which inspired some of the additions I made to my own Gulp file, including this cache hack.

The Imports

At the top of the file, I start by destructuring the necessary functions from Gulp. 

const { src, dest, watch, series, parallel } = require('gulp');

Then, I import all the modules I will be using for my tasks. I will provide more details on the role of these modules once I get to their specific tasks further down in the file.

const sourcemaps = require('gulp-sourcemaps');
const sass = require('gulp-sass');
const concat = require('gulp-concat');
const uglify = require('gulp-uglify');
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const imagemin = require('gulp-imagemin');
const gulpif = require('gulp-if');
const browserSync = require('browser-sync').create();
var replace = require('gulp-replace');

The Variables

Next, I will be setting up some variables that will help me in the construction of each task.

File paths variables 

I will use one variable here instead of multiple, and this is totally up to you of course. This files variable holds an object containing all the paths for the files used on the tasks. Note the file paths are constructed using globs. A glob is a string of literal and/or wildcard characters used to match file paths. To get into the weeds of globs see this page on the Gulp website.  

To specify multiple globs, use an array, like the JsPath property shown here.

const files = {
 htmlPath: 'builds/development/*.html',
 scssPath: 'components/sass/*.scss',
 jsPath: [
   'components/scripts/libraries/*.js',
   'components/scripts/*.js'
   // add more sources here if needed.
 ],
 imgPath: 'builds/development/images/**/*.*'
}

Other variables 

Here I am using a couple of miscellaneous variables to hold the cache bust string and the output directory, which I will go into next.

let cbString; //to use in cacheBust
let outputDir; //for output directory

Environment Variable

I am using this node property to let Gulp know where my files should end up, in the Development or Production directory (see file structure).

const env = process.env.NODE_ENV || 'production';

This conditional sets the outputDir variable based on the value of env. This way, I can perform certain tasks only when this is set to production, like minifying Js files.

//output directory check
if (env === 'development') {
 outputDir = 'builds/development/';
} else {
 outputDir = 'builds/production/';
}

The Tasks

And now to the tasks. Following are the 6 functions that comprise this file. Please note that these can be named any way you like.

In Gulp, you must return something from a task function (a stream, promise, event emitter, child process, or observable) in order for Gulp to know whether to stop or continue to the next task.

For most of the following tasks, we will be returning a stream. Specifically, using the src() and desc() functions.
So generally, we will use the src() function to create a stream by passing our file path. Then, the stream is piped (or connected) to other streams which will ultimately manipulate and change our files. Finally spitting out the new modified files using desc().

The HTML Task

In this task I am only doing one thing, and that is to modify the index.html file by adding a unique string to my css and js script tag. This will ensure the browser gets a fresh copy of the css and js files when we do any edits, instead of serving a cached version. 

Before we move on, make sure to include the following query string in your html file: cb= in both the css link and Js script tags.

<!-- === Styles ==== -->
<link rel="stylesheet" href="css/style.css?cb=12345">
 
<!-- === Js ==== -->
<script src="js/main.js?cb=12345"></script>

Of course you can choose to do more in this task, like minifying your html code for production, but I personally don’t like to do this. So we will only do this one operation for now.

function htmlTask(){
 cbString = new Date().getTime();
 return src(files.htmlPath)
     .pipe(gulpif(env === 'development', replace(/cb=\d+/g, 'cb=' + cbString)))
     .pipe(dest(outputDir));
}

First, the cache bust string variable, cbString, is set using the getTime() method from the Date object. This method returns the numeric value of the specified date as the number of milliseconds since January 1, 1970, 00:00:00 UTC. Since we are not passing a date value to Date() it will return the current date, in a format like: 2021-02-13T21:21:02.723Z, so adding getTime() returns the current date as the number of milliseconds, like so: 1613251262723.

Then, we are returning the modified stream produced by src() and its pipe() method . 

But, let’s go into src() and pipe() for a minute. We know that src() will create a stream by passing our file path. Then, in the first pipe method, we are using gulpif to check if we are in development, and if so we use replace() with a regular expression to find and replace cb= with cb=1613251262723 which is the result of 'cb=' + cbString. We then use the dest() function to output the modified html file back into the destination folder.

The SASS Task

In this task, I am performing quite a few more operations than the previous task. But the main thing I am accomplishing is compiling my Sass files to a css file and placing it in its own directory. 

function scssTask(){   
 return src(files.scssPath)
     .pipe(sourcemaps.init()) // initialize sourcemaps first
     .pipe(sass()) // compile SCSS to CSS
     .pipe(gulpif(env === 'production', postcss([autoprefixer(), cssnano()]))) // autopref & cssnano on prod
     .pipe(sourcemaps.write('.')) // write sourcemaps file in current directory
     .pipe(dest(outputDir + 'css')); // put final CSS in output folder
}

First thing you see is the initiation of source maps. Source maps are handy in order to know in which Sass file your css style is located in. Next, it compiles the scss files using sass(), and if in production, adds auto prefixes and minifies the css using postcss(). Then it will output source maps and the compiled css file to the output directory.

The Js Task

In the Js task I am combining and minifying all my Javascript files into a single file which I am calling main.js.

function jsTask(){
 return src(files.jsPath)
     .pipe(concat('main.js'))
     .pipe(gulpif(env === 'production', uglify())) //uglify on prod only
     .pipe(dest(outputDir + 'js'));
}

Note that we passed an array to the src() function containing all the paths to the Js files I specified in the files object. Using concat(), you can combine all these files together into one. 

 Then, if in production, I am minifying the file using uglify() before I write the main.js file to the output directory.

The Image Task

In the image task I am optimizing images for use in production.

function imgTask(){
 return src(files.imgPath)
   .pipe(gulpif(env === 'production', imagemin([
     imagemin.gifsicle({interlaced: true}),
     imagemin.mozjpeg({quality: 75, progressive: true}),
     imagemin.optipng({optimizationLevel: 5}),
     imagemin.svgo({
         plugins: [
             {removeViewBox: false},
             {cleanupIDs: false}
         ]
     })
   ],
   {
     verbose: true
   }
 )))
   .pipe(gulpif(env === 'production', dest(outputDir + 'images')));
}

First I check if I am in production mode using gulpif(), if so, I optimize images and place them in the corresponding output directory. Optimization is accomplished using the imagemin Gulp module, which includes the necessary optimizers to handle the most popular image files, like jpg, png, gifs, and svgs. Please check the link to find out about each plugin’s options.

The Watch Task

The watch task will do just that, watch the files for any changes and if any changes are made, it will execute a task.

function watchTask(){
 //start BrowserSync server
 server();
 
 //now watch each file
 watch(files.htmlPath, htmlTask).on('change', browserSync.reload);
 watch(files.scssPath, series(scssTask, htmlTask)).on('change', browserSync.reload);
 watch(files.jsPath, series(jsTask, htmlTask)).on('change', browserSync.reload);
 watch(files.imgPath, imgTask).on('change', browserSync.reload);
}

The first thing we do is to start the BrowserSync server using server(). Then, using Gulp watch, we will monitor our html, scss, js, and image files and if any changes occur we run the corresponding task, and also tell BrowserSync to reload the browser. Notice that on the scss and js watch I am using a series() function to run both the scss/js and the html tasks in series. I am doing this because I want to update that cache bust string after changing any styles or js file and that is done on my html task.

So, that was the final task on the file.


The following is the BrowserSync built-in server configurations.

function server() {
 browserSync.init({
   server: {
       baseDir: outputDir,
       index: "index.html"
       //directory: true
   },
   port: 8080, //<-- changing default port (default:3000);
   open: false, //<-- enable to prevent opening browser
 
   /*
   Open in specific browser
   (On MacOS check Applications folder for name of app) */
   browser: "FirefoxDeveloperEdition"
 });
}

Here we specify a few options:

  • baseDir : The base directory is where I am serving the files from, which in this case is my outputDir.
  • index : The index is the specific file I want to open when the server starts.
  • port : Is the port number I want to use. The default is 3000 if it’s not specified.
  • open : Allows you to choose if the browser should open automatically when the server starts. I want to open it manually so I have set it to false
  • browser : Specifies the browser I will like to open when I start the server. You can remove this line if you set open to false. To find out the exact name you should use here, check your Application folder if on the Mac or the Program Files directory if on Windows. If the browser is not specified, your default browser will be used.

Default Tasks

And finally, we will export our default tasks in a series() function which will run immediately after starting gulp from our command line. The first task I want to run is the htmlTask, then I want to run 3 tasks in parallel and for this Gulp provides us with an appropriately named function called parallel(). So all three tasks: scssTask, jsTask, imgTask will run simultaneously. And finally, I will run the watchTask to start monitoring the files. 

exports.default = series(
 htmlTask,
 parallel(scssTask, jsTask, imgTask),
 watchTask
);

And that’s all there is to it. With this file you will be able to build a Gulp workflow that allows you to run a local web server with the ability to compile sass files, concatenate and minify Js files, compress and optimize images, and refresh the browser and clear cache upon any edits. Also, remember to copy the package.json file if you decide to start with this Gulp file.

I hope this post helps you get more out of Gulp, or at least provides you with some ideas to implement on your new or existing Gulp file. If you have any feedback, suggestions, or questions please do leave them on the comment section below. I do read and try to respond to every comment. 

Thanks for reading!

Please Post Your Comments & Reviews

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.