Chapter 08 — Front-End Fundamentals

Task Runners

A task runner automates repetitive development tasks. Compiling Sass into CSS, bundling JavaScript files, optimizing images, watching for file changes and reloading the browser: these are things you’d otherwise do manually, every time, in the right order. A task runner does them for you.

Gulp is the task runner we’re using for this series. It’s been around since 2013, is still actively maintained, is widely used in WordPress development specifically, and its API is clean enough to understand without much background. It’s also not the only option: Webpack, Vite, Rollup, and even npm scripts alone can handle similar work. The concepts you learn here apply across all of them. The specific tool matters less than understanding what you’re automating and why.


Install Gulp

Gulp requires two installs: the CLI, which you install once globally, and the project package, which goes into your tutorials project.

bash~/tutorials/
npm install --global gulp-cli
npm install --save-dev gulp

Verify the install:

bash~/tutorials/
gulp --version

You should see both the CLI version and the local package version.


Create a Gulpfile

Gulp reads its instructions from a file called gulpfile.js in the root of your project. Create it:

bash~/tutorials/
touch gulpfile.js

Open it in VS Code. A Gulpfile is a JavaScript file that imports Gulp’s API and exports functions as tasks. Here’s the minimal structure:

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

function defaultTask(done) {
  console.log('Gulp is running');
  done();
}

exports.default = defaultTask;

Run it:

bash~/tutorials/
gulp

You should see Gulp is running in your terminal. That’s Gulp finding your gulpfile.js, finding the exported default task, and running it.


What Gulp actually does

Gulp works with streams. You point it at source files with src(), pipe those files through transformations, and write the result somewhere with dest().

js~/tutorials/
function copyHTML(done) {
  src('src/**/*.html')
    .pipe(dest('dist/'));
  done();
}

This task reads every HTML file in src/ and writes it to dist/. No transformation, just a copy. This is the basic shape of every Gulp task: source, pipe, destination.


Watch files for changes

Gulp’s watch() function runs a task automatically when specified files change:

js~/tutorials/
function watchFiles() {
  watch('src/**/*.html', copyHTML);
}

This watches every HTML file in src/ and runs copyHTML whenever one changes.


A working Gulpfile

Replace the contents of gulpfile.js with this. It handles the tasks you need right now: copying HTML, copying CSS, copying JS, and watching all three for changes. You’ll replace the CSS and JS tasks in the next two chapters.

js~/tutorials/
const { src, dest, watch, series, parallel } = require('gulp');
const browserSync = require('browser-sync').create();

// Copy HTML from src to dist
function html() {
  return src('src/**/*.html')
    .pipe(dest('dist/'));
}

// Copy CSS from src to dist
function css() {
  return src('src/**/*.css')
    .pipe(dest('dist/'));
}

// Copy JS from src to dist
function js() {
  return src('src/**/*.js')
    .pipe(dest('dist/'));
}

// Start a local server and watch for changes
function serve() {
  browserSync.init({
    server: {
      baseDir: './dist',
    },
  });

  watch('src/**/*.html', series(html, reload));
  watch('src/**/*.css', series(css, reload));
  watch('src/**/*.js', series(js, reload));
}

function reload(done) {
  browserSync.reload();
  done();
}

// Build: run html, css, and js in parallel
exports.build = parallel(html, css, js);

// Default task: build first, then serve
exports.default = series(parallel(html, css, js), serve);

Run Gulp:

bash~/tutorials/
gulp

Gulp builds your project into dist/, starts a browser-sync server pointing at dist/, and watches your src/ files. Edit your HTML or CSS, save, and the browser reloads automatically.


series and parallel

Two of the most useful parts of Gulp’s API:

series(taskA, taskB) runs tasks one after the other. Use this when order matters: build the CSS before reloading the browser.

parallel(taskA, taskB) runs tasks simultaneously. Use this when tasks are independent: building HTML, CSS, and JS at the same time is faster than building them sequentially.

Both accept any number of arguments and can be nested:

js~/tutorials/
exports.default = series(
  parallel(html, css, js),
  serve
);

This builds all three asset types in parallel, then starts the server once they’re done.


Update npm scripts

Update package.json so your common commands are accessible via npm run:

json~/tutorials/
"scripts": {
  "start": "gulp",
  "build": "gulp build"
}

Now npm run start runs your full development workflow and npm run build produces a clean build in dist/.


The dist/ folder

Your dist/ folder contains the built output of your project. This is what you’d deploy to a server. It should be in your .gitignore:

plaintext~/tutorials/
dist/

You don’t commit build output. You commit source files and let the build process generate it. Anyone who clones the project runs npm install and then npm run build to generate dist/ locally.

Verify dist/ is in your .gitignore, then commit:

bash~/tutorials/
git add .
git commit -m "Add Gulp build pipeline with browser-sync"

What you now know

You have a working build pipeline. Gulp is watching your source files, copying them to dist/, and refreshing the browser on every save. Right now it’s doing simple copies. In Chapter 9 you’ll replace the CSS task with one that compiles Sass, which is where the pipeline starts earning its keep.

Reference