Transpile and minify Javascript, HTML and CSS using Gulp 4

This guide seeks to help out those trying to set up Gulp javascript transpilation and minification. Since minification is mostly beneficial on the front-end side of the web, this article describes how to set up basic HTML and CSS minifcation as well.

The resulting gulpfile is available here as a gist for those who want to get things up and running immediately, however I do recommend writing along with the article to make it stick.

Javascript transpilation

The dependencies we will need to get modern javascript transpiled.

  • Our most obvious one, Gulp version 4+
  • Using Browserify we will be able to bundle modules into a single file (a bundle).
  • To be able to use modern javascript in our browser we transpile our bundle into javascript understood by current and last-gen browsers, we will be using Babel to transform the code before bundling. For this we just need @babel/core.
  • Using the @babel/preset-env preset we specify which browsers we would like to target the transformation for. Check out the documentation how to target specific browser versions, without setting a target it will default to transforming ECMAScript 2015+ code.
  • We will use the Babelify transform so that Browserify can use Babel to transform each file before bundling them up.  (I recommend checking out this list of transforms for more possibilities)
  • We need to use vinyl-source-stream to turn our bundle into something which gulp understands to be able to write it to a file.

Let’s intall these dependencies and write the basis of our gulpfile.

$ npm install --save-dev gulp browserify babelify @babel/core @babel/preset-env vinyl-source-stream
const gulp = require("gulp");
const browserify = require("browserify");
const babelify = require("babelify");
const source = require("vinyl-source-stream");

// To prevent rewriting the source and build folder locations
const paths = {
	source: "./src",
	build: "./build"
};

// Let's write our task in a function to keep things clean
function javascriptBuild() {
	// Start by calling browserify with our entry pointing to our main javascript file
	return (
		browserify({
			entries: [`${paths.source}/scripts/main.js`]
			// Pass babelify as a transform and set its preset to @babel/preset-env
			transform: [babelify.configure({ presets: ["@babel/preset-env"] })]
		})
			// Bundle it all up!
			.bundle()
			// Source the bundle
			.pipe(source("bundle.js"))
			// Then write the resulting files to a folder
			.pipe(gulp.dest(`${paths.build}/scripts`))
	);
}

// Expose the task, this allows us to call this task
// by running `$ gulp build' in the terminal
exports.build = javascriptBuild;

We did it! Our build task now transpiles and bundles our javascript, not too shabby. 

Javascript minification

Unfortunately the transpiled code is far too good looking, the world wide web appreciates performant, not pretty code. So we will be making an effort to uglify our transpiled code. Let’s take a look at our dependencies to make minification happen.

  • While vinyl-source-stream does a good job turning the bundle into a stream other plugins prefer to receive text in a buffer, we will use vinyl-buffer for this.
  • Quite simple, we pipe into gulp-uglify a buffer of pretty code, and out comes ugly code who would have guessed!
$ npm install --save-dev vinyl-buffer gulp-uglify
const gulp = require("gulp");
const browserify = require("browserify");
const babelify = require("babelify");
const source = require("vinyl-source-stream");
const buffer = require("vinyl-buffer");
const uglify = require("gulp-uglify");

function javascriptBuild() {
	return (
		browserify({
			entries: [`${paths.source}/scripts/main.js`]
			transform: [babelify.configure({ presets: ["@babel/preset-env"] })]
		})
			.bundle()
			.pipe(source("bundle.js"))
			// Turn it into a buffer!
			.pipe(buffer())
			// And uglify
			.pipe(uglify())
			.pipe(gulp.dest(`${paths.build}/scripts`))
	);
}

exports.build = javascriptBuild;

HTML minification

Okay, not bad. We set up javascript transpilation and minification, but I doubt you will be using minified code on the back-end. We can do better, on to HTML minification!

All we need is a single dependency, gulp-htmlmin it’s all in the name really. Its implementation is just as straightforward, don’t forget to edit the exposed task so both HTML and Javascript get minified!

$ npm install --save-dev gulp-htmlmin
const htmlmin = require("gulp-htmlmin");

// Write our html task in a seperate function
function htmlBuild() {
	return gulp
		.src(`${paths.source}/*.html`)
		.pipe(htmlmin())
		.pipe(gulp.dest(paths.build));
}

// We have to change our exposed task, these functions can be ran in parallel as they do not depend on eachother.
// If your functions should run synchronously use gulp.series()
exports.build = gulp.parallel(javascriptBuild, htmlBuild);

CSS minification

Assuming you’re styling your website, you will have to minify your stylesheets as well.

  • The gulp-postcss package enables other packages to transform and modify our CSS files.
  • CSSNano is a PostCSS plugin used for minification, check out its documentation for more options and plugins to optimize its results with your project constraints in mind.

Again, we should keep in mind to edit our exposed task.

$ npm install --save-dev gulp-postcss cssnano
const postcss = require("gulp-postcss");
const cssnano = require("cssnano");

function cssBuild() {
	return gulp
		.src(`${paths.source}/styles/**/*.css`)
		.pipe(postcss([cssnano()]))
		.pipe(gulp.dest(`${paths.build}/styles`));
}

exports.build = gulp.parallel(javascriptBuild, cssBuild, htmlBuild);

Your end result should be a better optimized website, at least all of your code. I recommend looking into compressing your images as they usually make up the bulk of the size on a webpage. Be sure to check back soon, I will be writing a quick guide to image compression using Gulp ASAP!

Keeping things tidy

Our tasks should work perfectly, but we are not cleaning after ourselves right now. As it happens during a project, some HTML and CSS source files get deleted or moved as it updates, but these tasks keep filling up the build folder (leaving old files if not overwritten).

We can write a task to remove the build folder before each build using the del package.

$ npm install --save-dev del
const del = require("del");

function cleanup() {
	// Simply execute del with the build folder path
	return del([paths.build]);
}

// We have to run the cleanup task first, after which we can run the build tasks 
exports.build = gulp.series(cleanup, gulp.parallel(javascriptBuild, htmlBuild, cssBuild));

Convenience

Just a small last change, typing gulp build is fine and all. But since it’s our only task let’s make it our default task.

exports.default = exports.build = gulp.series(cleanup, gulp.parallel(javascriptBuild, htmlBuild, cssBuild));

Now you can either run the task by running gulp build or just gulp. 

Related articles