Gulp 4 Tutorial
Tags: gulp4, javascript, tutorial
Published: 2021-06-20 11:09:42

**Table of Contents**

- [What is Gulp?](#what-is-gulp)
- [Common Tasks](#common-tasks)
- [Prerequisites](#prerequisites)
- [Create package.json](#create-packagejson)
- [Install Packages](#install-packages)
- [Create gulpfile.js](#create-gulpfilejs)
- [Import Packages](#import-packages)
- [Add Paths](#add-paths)
- [Create Tasks](#create-tasks)
  - [Copy Files](#copy-files)
  - [Optimize Images](#optimize-images)
  - [Compile Styles](#compile-styles)
  - [Minify Scripts](#minify-scripts)
  - [Cache Busting](#cache-busting)
  - [Watch Files](#watch-files)
- [Export Tasks](#export-tasks)
- [Run Tasks](#run-tasks)
- [My gulpfile.js](#my-gulpfilejs)

## What is Gulp?

- Gulp is an open source Javascript toolkit and task runner
- It was built on Node.js and NPM
- Used for time consuming and repetitive tasks
- Hundreds of plugins available for different tasks

## Common Tasks

- Minification of styles and scripts
- Concatenation
- Cache busting
- Testing, linting and optimization

## Prerequisites

Install [Node.js](https://nodejs.org/en/download/)

Install Gulp globally using following command in command line.
By default latest version will get installed.

```bash
npm install -g gulp
```

Check if Gulp is installed properly, Then we are good to move forward.

```bash
npm gulp --version
```

## Create package.json

First create new directory for our project, if you already have one no need to create new.
Open command prompt and change current working directory as our project directory using below command (change path accordingly).

```bash
# Absolute path of directory
cd c:/projects/my-project
```

Now create `package.json` file for our project using below command.

```bash
npm init -y
```

## Install Packages

Install required packages and save them as development dependencies using below command.

```bash
npm install --save-dev gulp gulp-concat gulp-rename gulp-replace gulp-imagemin gulp-sourcemaps gulp-sass postcss gulp-postcss autoprefixer cssnano gulp-terser
```

Find below, Purpose of installed packages.

`gulp gulp-concat gulp-rename gulp-replace` - Basic file operations such as concatenation, file renaming and file content replacement.

`gulp-imagemin` - Image optimization.

`gulp-sourcemaps` - Sourcemaps creation for styles and scripts.

`gulp-sass postcss gulp-postcss autoprefixer cssnano` - sass/scss compilation, Add vendor prefixes and minify styles.

`gulp-terser` - Minify scripts.

After this our `package.json` file will contain data something like below.

```json
  "devDependencies": {
    "autoprefixer": "^10.2.5",
    "cssnano": "^5.0.2",
    "gulp": "^4.0.2",
    "gulp-concat": "^2.6.1",
    "gulp-imagemin": "^7.1.0",
    "gulp-postcss": "^9.0.0",
    "gulp-rename": "^2.0.0",
    "gulp-replace": "^1.1.3",
    "gulp-sass": "^4.1.0",
    "gulp-sourcemaps": "^3.0.0",
    "gulp-terser": "^2.0.1",
    "postcss": "^8.2.15"
  }
```

## Create gulpfile.js

Open project in code editor and create `gulpfile.js`.
In this file we will define all the tasks that we want to automate.

## Import Packages

Write below code inside `gilpfile.js`.
It will import all those installed packages, So that we can use them to perform tasks.

```javascript
const gulp = require('gulp');
const { src, dest, watch, series, parallel } = require('gulp');
const imagemin = require('gulp-imagemin');
const sourcemaps = require('gulp-sourcemaps');
const concat = require('gulp-concat');
const rename = require('gulp-rename');
const replace = require('gulp-replace');
const terser = require('gulp-terser');
const sass = require('gulp-sass');
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
```

## Add Paths

Add below object of paths into `gulpfile.js`.
We are using this object because its the most convenient way to handle paths. It adds maintainability to our code.

```javascript
const paths = {
  html: {
    src: ['./src/**/*.html'],
    dest: './dist/',
  },
  images: {
    src: ['./src/content/images/**/*'],
    dest: './dist/content/images/',
  },
  styles: {
    src: ['./src/scss/**/*.scss'],
    dest: './dist/css/',
  },
  scripts: {
    src: ['./src/js/**/*.js'],
    dest: './dist/js/',
  },
  cachebust: {
    src: ['./dist/**/*.html'],
    dest: './dist/',
  },
};
```

**Selectors**

| Pattern              | Match                                                                               |
| -------------------- | ----------------------------------------------------------------------------------- |
| \*.scss              | Matches any file ending with `.scss` from current directory                         |
| \*/.scss             | Matches any file ending with `.scss` from current directory and any child directory |
| \*.+(scss&#124;sass) | Matches any file ending with `.scss` or `.sass` from current directory              |

**Folder Structure**

We will keep all our files in `src` directory and then using Gulp tasks we will create optimized files in `dist` directory.
Above [paths](#add-paths) object is created based on this folder structure.

![Folder Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ejxvydj7yni2slye9nlg.PNG)

## Create Tasks

Gulp performs actions on files in pipeline. Pipeline / `.pipe()` operator is nothing but chained processes running synchronously.
Files are not affected until all processes are completed.
Gulp `.src()` and `.dest()` operators are used to read and write files. 

Below is the basic syntax for creating a Gulp task.

```javascript
function task_name() {
  return src('source-location') // Select files from given location
    .pipe(gulpPlugin()) // Send files through a gulp plugin
    .pipe(dest('destination-location')); // Write modified files at given location
}
```

### Copy Files

Copy HTML files from src to dest location using below function.
We can add more function like this in case we want to copy some other files such as fonts or media files.
For that, All we need to do is add paths in the [paths](#add-paths) object.
Then create new function just as below and update src and dest paths.

```javascript
function copyHtml() {
  return src(paths.html.src).pipe(dest(paths.html.dest));
}
```

### Optimize Images

Optimize all types of images(.png, .jpeg, .gif, .svg).
If any error occurs during performing the task, We will be logging that to the console.

```javascript
function optimizeImages() {
  return src(paths.images.src)
    .pipe(imagemin().on('error', (error) => console.log(error)))
    .pipe(dest(paths.images.dest));
}
```

### Compile Styles

Compile sass/scss and convert them into optimized css.
Keep all the tasks that needs to be done on styles between `sourcemaps.init()` and `sourcemaps.write('.')`, Otherwise [sourcemaps](https://dev.to/ganeshtyjo/source-maps-4h41) won't be generated properly.

We are using `cssnano` to optimize our styles that's why we are renaming files with `.min` suffix (Indicating minified).

```javascript
function compileStyles() {
  return src(paths.styles.src)
    .pipe(sourcemaps.init())
    .pipe(sass().on('error', sass.logError))
    .pipe(postcss([autoprefixer(), cssnano()]))
    .pipe(rename({ suffix: '.min' }))
    .pipe(sourcemaps.write('.'))
    .pipe(dest(paths.styles.dest));
}
```

### Minify Scripts

Optimize scripts using below function.

```javascript
function minifyScripts() {
  return src(paths.scripts.src)
    .pipe(sourcemaps.init())
    .pipe(terser().on('error', (error) => console.log(error)))
    .pipe(rename({ suffix: '.min' }))
    .pipe(sourcemaps.write('.'))
    .pipe(dest(paths.scripts.dest));
}
```

### Cache Busting

Browser cache files which we link in our `.html` files and next time use cached files whenever request is made for the same `.html` page.
This sometimes creates issue like, Browser still uses old cached files even if we have done changes in the files. This is known as browser caching issue.

Cache busting solves the browser caching issue by using a unique file version identifier to tell the browser that a new version of the file is available. Therefore the browser doesn't retrieve the old file from cache but rather makes a request to the origin server for the new file.

To make this work we will add `cache_bust` parameter (with any number) to all style and script urls in our `.html` files.

```html
<html>
  <head>
    <link rel="stylesheet" href="/dist/css/style.min.css?cache_bust=123" />
  </head>
  <body>
    <script src="/dist/js/script.min.js?cache_bust=123"></script>
  </body>
</html>
```

Now using below function we will replace these `cache_bust` parameter value with unique number. So that these urls will be unique, Browser will know that url is changed and instead of using already cached file it will request for the file from the server.

```javascript
function cacheBust() {
  return src(paths.cachebust.src)
    .pipe(replace(/cache_bust=\d+/g, 'cache_bust=' + new Date().getTime()))
    .pipe(dest(paths.cachebust.dest));
}
```

### Watch Files

Watch for file modification at specific paths and run respective tasks accordingly.

```javacsript
watch('path-to-watch',[tasks-to-perform]);
```

We can perform as many tasks as we want using `series()` and `parallel()` operators.
`series()` will execute tasks synchronously and `parallel()` will execute tasks asynchrously.

```javascript
function watcher() {
  watch(paths.html.src, series(copyHtml, cacheBust));
  watch(paths.images.src, optimizeImages);
  watch(paths.styles.src, parallel(compileStyles, cacheBust));
  watch(paths.scripts.src, parallel(minifyScripts, cacheBust));
}
```

## Export Tasks

Export created tasks so that they will be accessible to execute from command line.

```javascript
exports.copyHtml = copyHtml;
exports.optimizeImages = optimizeImages;
exports.compileStyles = compileStyles;
exports.minifyScripts = minifyScripts;
exports.cacheBust = cacheBust;
exports.watcher = watcher;
```

Create one default task which will execute all the tasks that we want and later starts watching files for modification.

```javascript
exports.default = series(
  parallel(copyHtml, optimizeImages, compileStyles, minifyScripts),
  cacheBust,
  watcher
);
```

## Run Tasks

Run any specific task using below command.

```bash
gulp task_name
```

Run default task using below command.

```bash
gulp
```

To stop `watcher` task, which continuously watch for file modifications, press `ctrl + c` in command line and answer asked question as `Y`.

```bash
^CTerminate batch job (Y/N)? Y
```

## My gulpfile.js

Check out my [gulpfile.js](https://github.com/ganesh-tyjo/gulp-app/blob/master/gulpfile.js) for reference.