Gulp configuration for WordPress – Part II

Posts

Here we are for the second part of the tutorial to configure Gulp for the optimal development of WordPress.
If you missed the first part, where we have installed all the modules needed for the tasks we will need, you can find it here.

I remind you that we will write our configuration file gulpfile.babel.js in JavaScript ES6, as I explained in the first part of this tutorial.

Importing Modules

Let’s start now, importing the downloaded modules into our file gulpfile.babel.js.

import { src, dest, watch, parallel, series } from 'gulp';
import yargs from 'yargs';
import sass from 'gulp-sass';
import cleanCss from 'gulp-clean-css';
import gulpif from 'gulp-if';
import postcss from 'gulp-postcss';
import sourcemaps from 'gulp-sourcemaps';
import autoprefixer from 'autoprefixer';
import imagemin from 'gulp-imagemin';
import webpack from 'webpack-stream';
import del from 'del';
import browserSync from "browser-sync";

We also create the constant that will help us determine if Gulp will have to work in development or production mode.

const PRODUCTION = yargs.argv.prod;

Task style

Let’s write our first task, which will be used to process our SCSS.

The task is set to detect any changes in the SCSS files in the src folder, and to compile, add prefixes cross-browsers and minify (in case we want to create the build for production) the bundle.scss file.

If you were wondering what exactly server.stream() does, it is used to directly inject the CSS into our page without reloading. It is a feature of BrowserSync, we’ll see later.

export const styles = () => {
    return src('src/scss/bundle.scss')
        .pipe(gulpif(!PRODUCTION, sourcemaps.init()))
        .pipe(sass().on('error', sass.logError))
        .pipe(gulpif(PRODUCTION, postcss([ autoprefixer ])))
        .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
        .pipe(gulpif(!PRODUCTION, sourcemaps.write()))
        .pipe(dest('dist/css'))
        .pipe(server.stream());
};

Task images

The second task that we will write is the one necessary to compress the images.

export const images = () => {
    return src('src/images/**/*.{jpg,jpeg,png,svg,gif}')
        .pipe(gulpif(PRODUCTION, imagemin()))
        .pipe(dest('dist/images'));
};

Copy and clean functions

We also need to copy the CSS file, JS and the images processed in the dist folder that we will feed to WordPress.

We will also benefit from a function that cleans the dist folder every time we start our development work.

export const copy = () => {
    return src(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'])
        .pipe(dest('dist'));
};

export const clean = () => del(['dist']);

Task scripts

Images and SCSS are fine, now we move on to JavaScript.

export const scripts = () => {
    return src('src/js/bundle.js')
        .pipe(webpack({
            module: {
                rules: [
                    {
                        test: /\.js$/,
                        use: {
                            loader: 'babel-loader',
                            options: {
                                presets: []
                            }
                        }
                    }
                ]
            },
            mode: PRODUCTION ? 'production' : 'development',
            devtool: !PRODUCTION ? 'inline-source-map' : false,
            output: {
                filename: 'bundle.js'
            },
        }))
        .pipe(dest('dist/js'));
};

BrowserSync

We use BrowserSync to automatically reload the page in case we modify a PHP or JS file, while we use server.stream() that we added to the task style to directly inject the CSS into the page, without the need to reload.

We point the BrowserSync server on our local server, indicating in proxy our domain.

As I explained in this post, I suggest you to use a different domain from .local, to avoid problems of slowdown in the update of the BrowserSync server that would frustrate all the work.

const server = browserSync.create();
export const serve = done => {
    server.init({
        proxy: "https://project.dev/" // your local website link
    });
    done();
};

export const reload = done => {
    server.reload();
    done();
};

Task watch

Obviously we want everything to be automated, and with the task watch we can tell gulp to start tasks whenever we update a file.

export const watchForChanges = () => {
    watch('src/scss/**/*.scss', series(styles));
    watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', series(images, reload));
    watch(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'], series(copy, reload));
    watch('src/js/**/*.js', series(scripts, reload));
    watch("**/*.php", reload);
};

Final step

Finally, let’s add all our tasks in the build and dev tasks.

We will use build to compile the files ready for deployment, while dev will be the task we will use throughout the development phase.

As already explained, the distinction is useful to be able to work faster in development, avoiding unnecessary tasks to Gulp as the one in minifying files.

export const dev = series(clean, parallel(styles, images, copy, scripts), serve, watchForChanges);
export const build = series(clean, parallel(styles, images, copy, scripts));
export default dev;

Then add the tasks to the file package.json so you have both tasks at hand

  "scripts": {
    "start": "gulp",
    "build": "gulp build --prod"
  },

Conclusions

Finally, here is the file gulpfile.babel.js in its entirety:

import { src, dest, watch, parallel, series } from 'gulp';
import yargs from 'yargs';
import sass from 'gulp-sass';
import cleanCss from 'gulp-clean-css';
import gulpif from 'gulp-if';
import postcss from 'gulp-postcss';
import sourcemaps from 'gulp-sourcemaps';
import autoprefixer from 'autoprefixer';
import imagemin from 'gulp-imagemin';
import webpack from 'webpack-stream';
import del from 'del';
import browserSync from "browser-sync";

const PRODUCTION = yargs.argv.prod;

export const styles = () => {
    return src('src/scss/bundle.scss')
        .pipe(gulpif(!PRODUCTION, sourcemaps.init()))
        .pipe(sass().on('error', sass.logError))
        .pipe(gulpif(PRODUCTION, postcss([ autoprefixer ])))
        .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
        .pipe(gulpif(!PRODUCTION, sourcemaps.write()))
        .pipe(dest('dist/css'))
        .pipe(server.stream());
};

export const images = () => {
    return src('src/images/**/*.{jpg,jpeg,png,svg,gif}')
        .pipe(gulpif(PRODUCTION, imagemin()))
        .pipe(dest('dist/images'));
};

export const copy = () => {
    return src(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'])
        .pipe(dest('dist'));
};

export const clean = () => del(['dist']);

export const scripts = () => {
    return src('src/js/bundle.js')
        .pipe(webpack({
            module: {
                rules: [
                    {
                        test: /\.js$/,
                        use: {
                            loader: 'babel-loader',
                            options: {
                                presets: []
                            }
                        }
                    }
                ]
            },
            mode: PRODUCTION ? 'production' : 'development',
            devtool: !PRODUCTION ? 'inline-source-map' : false,
            output: {
                filename: 'bundle.js'
            },
        }))
        .pipe(dest('dist/js'));
};

const server = browserSync.create();
export const serve = done => {
    server.init({
        proxy: "https://project.dev/" // your local website link
    });
    done();
};
export const reload = done => {
    server.reload();
    done();
};

export const watchForChanges = () => {
    watch('src/scss/**/*.scss', series(styles));
    watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', series(images, reload));
    watch(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'], series(copy, reload));
    watch('src/js/**/*.js', series(scripts, reload));
    watch("**/*.php", reload);
};

export const dev = series(clean, parallel(styles, images, copy, scripts), serve, watchForChanges);
export const build = series(clean, parallel(styles, images, copy, scripts));
export default dev;

All we have to do is import the bundle.js and bundle.css files into WordPress, adding the import function to functions.php

function _yourtheme_assets() {
  wp_enqueue_style( '_yourtheme-stylesheet', get_template_directory_uri() . '/dist/css/bundle.css', array(), '1.0.0', 'all' );
  
  wp_enqueue_script( '_yourtheme-scripts', get_template_directory_uri() . '/dist/js/bundle.js', array(), '1.0.0', true );
}
add_action('wp_enqueue_scripts', '_yourtheme_assets');

Remember to edit _yourtheme with the name of your theme.

You just have to work on your new project, giving npm run start from the terminal during the development phase, and npm run build for the final build ready for deployment.


Photo by Franck V. on Unsplash