Frontend

While on my journey of building a new website using Umbraco 9, I decided to explore the frontend side of things a little. I wanted a way to simplify the frontend build tasks required to generate production ready frontend code into a dist folder. After analysing the current frontend task runners on the market, I decided to go with Gulp 4.0.2. I then drew up a list of the required tasks that I needed to achieve this:

  1. SCSS Task - Compile SASS files (*.SCSS) into a minified CSS file in the dist folder.
  2. JavaScript Task - Compile multiple JavaScript files into a minified and compressed single JavaScript file in the dist folder.
  3. Image Squash - Compress all images and copy to the dist folder.
  4. Handlerbars To HTML - Convert Handlebars files and partials into HTML.
  5. Watch/Live Reloading - Watch all HTML, JS and CSS files and perform the live reload to the page in all browsers whenever files were changed.

Node.js

The first step is to install Node.js, you can do this by downloading and installing Node.js from their website. This will then give you access to Node Package Manager (NPM).

Install NPM Packages

Once Node.js is installed you'll need to install the packages that we require for our frontend project. Using Powershell or CMD, go to the root of your frontend project and run the following npm installs. This will then update your packages.json file with these dependencies. You are now ready to start writing your Gulpfile.js file.

npm install gulp --save-dev
npm install gulp-sass --save-dev
npm install sass --save-dev
npm install gulp-clean-css --save-dev
npm install gulp-uglify --save-dev
npm install browser-sync --save-dev
npm install gulp-compile-handlebars --save-dev
npm install gulp-rename --save-dev
npm install [email protected] --save-dev

For reference here is my folder structure for my frontend project:

Frontend Folder Structure

SCSS Task

This task is quite simple, it takes the src scss file and runs it through 3 pipes. The first pipe converts the SASS to a CSS file, the second minifies the CSS file and finally the last pipe saves the CSS file into the /dist/css/ folder.

// Sass Task
function scssTask() {
    return src('scss/style.scss', { sourcemaps: true })
        .pipe(sass())
        .pipe(cleanCSS())
        .pipe(dest('dist/css', { sourcemaps: '.' }));
}

JavaScript Task

Another simple task, this takes the src main.js file specified and runs it through the uglify() pipe which minifies and compresses the JavaScript file. Then finally the resulting file is then copied to the /dist/js/ folder.

// JavaScript Task
function jsTask() {
    return src('js/main.js', { sourcemaps: true })
        .pipe(uglify())
        .pipe(dest('dist/js', { sourcemaps: '.' }));
}

Image Squash

The src specifies which image folders and files to be considered for compression, then using the NPM package gulp-imagemin we compress all the images. Then finally copy them to the /dist/images/ folder.

function imageSquash() {
    return src(["images/*", "images/**/*"])
        .pipe(imagemin())
        .pipe(dest("dist/images"));
}

Handlebars to HTML

The hbsToHtml task took a little bit more time to configure correctly, but is extremely powerful tool to have in order to make life easier when building and structuring your frontend website with layouts and partials. This set-up probably requires and blog post of its own, however I'll go over the basics here.

The src specifies the folder to find the handlebar pages that we want to convert to HTML pages. The next pipe then initialises the handlebars compiler and specifies where to find the partials that need replacing. The generated hbs files are then renamed from hbs to HTML. Then finally the HTML files and copied to the dist folder.

function hbsToHtml() {
    return src('./handlebars/pages/*.hbs')
        .pipe(handlebars({}, {
            ignorePartials: true,
            batch: ['./handlebars/partials']
        }))
        .pipe(rename({
            extname: '.html'
        }))
        .pipe(dest('./dist'));
}

Browser Sync

In order to load the frontend project in a browser to view and test we are using the browser-sync NPM package. The browsersyncServe function initialises browser sync and specifies the base directory to serve files from.

The next function, browsersyncReload, simple reloads the browser page so we can see the refreshed changes after making any modifications to the code base.

function browsersyncServe(cb) {
    browsersync.init({
        server: {
            baseDir: './dist'
        }
    });
    cb();
}

function browsersyncReload(cb) {
    browsersync.reload();
    cb();
}

Watch Tasks

The final piece of the jigsaw is to implement the watch tasks, this essentially watches for file changes, runs the specified functions and reloads the browser. Here I have specified two  watch tasks, one for watching  file modifications to any of the handlerbar pages and partials. A second, for watching for changes to the SCSS, images and JavaScript files. If there are any changes to these files then the corresponding functions are run, using the series() method, to update the dist folder. Then the browser is reloaded using the browsersyncReload function.

// Watch Task
function watchTask() {
    watch(['handlebars/pages/*.hbs', 'handlebars/partials/**/*.hbs'], series(hbsToHtml, browsersyncReload));
    watch(['scss/*.scss', 'images/**/*', 'images/*', 'js/*.js'], series(scssTask, jsTask, imageSquash, browsersyncReload));
}

Gulp & Gulp Dev Tasks

To launch the frontend project into your default browser you can run 'gulp' at the root using PowerShell or CMD. This will run the exports.default and the containing task functions. If you have a index.html file then this will be load first.

I set-up another gulp task 'gulp dev' to generate the files for the dist folder and then copy these to my backend project.

// Default Gulp Task - gulp
exports.default = series(
    scssTask,
    jsTask,
    imageSquash,
    hbsToHtml,
    browsersyncServe,
    watchTask
)

// gulp dev
exports.dev = series(
    scssTask,
    jsTask,
    imageSquash,
    hbsToHtml,
    copyDist
)

Gulpfile.js

To see the complete gulpfile.js code take a look below:

const { src, dest, watch, series } = require('gulp');
const sass = require('gulp-sass')(require('sass'));
const cleanCSS = require('gulp-clean-css');
const uglify = require('gulp-uglify');
const browsersync = require('browser-sync').create();
const handlebars = require('gulp-compile-handlebars');
const rename = require('gulp-rename');
const imagemin = require('gulp-imagemin');

// Sass Task
function scssTask() {
    return src('scss/style.scss', { sourcemaps: true })
        .pipe(sass())
        .pipe(cleanCSS())
        .pipe(dest('dist/css', { sourcemaps: '.' }));
}

// JavaScript Task
function jsTask() {
    return src('js/main.js', { sourcemaps: true })
        .pipe(uglify())
        .pipe(dest('dist/js', { sourcemaps: '.' }));
}

function imageSquash() {
    return src(["images/*", "images/**/*"])
        .pipe(imagemin())
        .pipe(dest("dist/images"));
}

function browsersyncServe(cb) {
    browsersync.init({
        server: {
            baseDir: './dist'
        }
    });
    cb();
}

function browsersyncReload(cb) {
    browsersync.reload();
    cb();
}

function copyDist() {
    return src(["dist/**/*", "dist/**/**/*"], { base: "." })
        .pipe(dest('../src/DanMcilroy.Site/bin/Debug/net5.0/publish/wwwroot/'))
        .pipe(dest('../src/DanMcilroy.Site/wwwroot/'))
        .pipe(dest('../src/DanMcilroy.Site/'));
};

function hbsToHtml() {
    return src('./handlebars/pages/*.hbs')
        .pipe(handlebars({}, {
            ignorePartials: true,
            batch: ['./handlebars/partials']
        }))
        .pipe(rename({
            extname: '.html'
        }))
        .pipe(dest('./dist'));
}

// Default Gulp Task - gulp
exports.default = series(
    scssTask,
    jsTask,
    imageSquash,
    hbsToHtml,
    browsersyncServe,
    watchTask
);

// gulp dev
exports.dev = series(
    scssTask,
    jsTask,
    imageSquash,
    hbsToHtml,
    copyDist
);

// Watch Task
function watchTask() {
    watch(['handlebars/pages/*.hbs', 'handlebars/partials/**/*.hbs'], series(hbsToHtml, browsersyncReload));
    watch(['scss/*.scss', 'images/**/*', 'images/*', 'js/*.js'], series(scssTask, jsTask, imageSquash, browsersyncReload));
}