Caution!

This is an old post. Information here may be out-dated, or the post may re­flect opin­ions or be­liefs I no longer share.

Pre-requisites

Introduction

The fever over task-run­ners like Grunt, Gulp and Broccoli has largely died in 2017 due to su­pe­rior al­ter­na­tives such as Webpack. However, if you’re like me, you’re still us­ing Gulp sim­ply be­cause it works for you and you’re not work­ing on big pro­jects or in big teams to ne­ces­si­tate the change to Webpack.

Since ini­tially learn­ing about LiveReload in 2015, I have moved to Firefox (and for the most part, ap­pre­ci­ate it). This means I do not have ac­cess to an of­fi­cial ad­don as I did on Chrome. Not only this, I nei­ther want to in­stall an ad­di­tional ex­ten­sion for some­thing I do not do 24x7 (hobby web de­vel­oper), nor bloat my Firefox in­stal­la­tion, es­pe­cially if I can help it.

The prob­lem

If you read the ar­ti­cle on im­ple­ment­ing LiveReload, you must have come across the fol­low­ing sec­tion:

Next, we need to down­load the Google Chrome ex­ten­sion LiveReload, go to the Chrome Store and down­load it here. Make sure you can view it in your tool bar and that the cir­cle is filled in with black. This is im­por­tant or else it won’t work.

From dmitriz’s gulp-au­toma­tion pro­ject, which in­spired me to un­der­stand his code and im­ple­ment my own from scratch, you can see why he ar­gues for a dif­fer­ent ap­proach:

Many of them are based on in­stalling a Chrome browser ex­ten­sion. However, in ad­di­tion to be­ing lim­ited to the Chrome browser, ex­ten­sions are po­ten­tially vul­ner­a­ble. Extensions can also be guilty to slow down your browser, by adding un­nec­es­sary and slow run­ning scripts block­ing your use­ful con­tent. As ex­ten­sion runs on every page in your browser, it does­n’t seem to be a good fit for the pur­pose of LiveReload.

Fixing our prob­lem

For the more rel­e­vant part, let’s start dis­cussing what we are go­ing to do to solve the prob­lem.

Run a server on Node us­ing Gulp

We are go­ing to use connect as a server.

npm install --save-dev connect

Open your gulpfile.js, re­quire connect and write a new task called server:

// Require the connect npm plugin
var connect = require("connect");
// Initialize connect
var connectApp = connect();

// connect server
gulp.task("server", function() {
return connectApp.listen(8080);
});

For now, the con­nect server can­not do any­thing. It knows to lis­ten to port 8080 but does not know what the root of the web­site/​app is. I’m serv­ing a sta­tic web­site that out­puts to build/ and so will con­fig­ure it as such us­ing the serve-static mid­dle­ware for connect:

npm install --save-dev serve-static

Editing our gulpfile.js to re­flect the new changes:

// Require the connect npm plugin and any additional middleware
var connect = require("connect");
var connectServeStatic = require("serve-static");
// Initialize connect
var connectApp = connect();

// connect server
gulp.task("server", function() {
return connectApp.use(connectServeStatic("build/")).listen(8080);
});

Now our connect server knows to server sta­tic files from build/ when re­quested over port 8080.

Set up base for LiveReload to use

We’ll add connect-livereload to our pro­ject. All it lit­er­ally does is in­ject a piece of code to your files while the connect server is run­ning. This means that your built files will not in­clude this piece of code.

Since it is a mid­dle­ware for our connect server, we’ll use it so.

Install it as usual:

npm install --save-dev connect-livereload

Require the mod­ule in your gulpfile.js and mod­ify the server task to make use of it.

// Require the connect npm plugin and any additional middleware
var connect = require("connect");
var connectServeStatic = require("serve-static");
var connectLiveReload = require("connect-livereload");
// Initialize connect
var connectApp = connect();

// connect server
gulp.task("server", ["html", "scss", "js"], function() {
return connectApp
.use(connectLiveReload({ port: 35729 }))
.use(connectServeStatic("build/"))
.listen(8080);
});

If you re­fresh your page now, you will see the fol­low­ing code in­jected be­fore the close of your body tag:

<script
src="//localhost:35729/livereload.js?snipver=1"
async=""
defer=""
>
</script>

connect-livereload does not pro­vide the ac­tual script, it merely in­jects the code to make it avail­able for gulp-livereload to use, as we’ll see in the next sec­tion.

Start lis­ten­ing for changes

Since you’re here, you must be ac­cus­tomed to gulp.watch.

Currently, this is what our watch task looks like:

gulp.task("watch", ["server"], function() {
gulp.watch("source/**/*.html", ["html"]);
gulp.watch("source/assets/**/*.scss", ["scss"]),
gulp.watch("source/assets/**/*.js", ["js"]);
});

We’ll in­stall gulp-livereload and mod­ify our gulpfile.js ac­cord­ingly:

npm install --save-dev gulp-livereload
var gulpLiveReload = require("gulp-livereload");

gulp.task("watch", ["server"], function() {
// start the LiveReload server
gulpLiveReload.listen();
gulp.watch("source/**/*.html", ["html"]);
gulp.watch("source/assets/**/*.scss", ["scss"]),
gulp.watch("source/assets/**/*.js", ["js"]);
});

Call LiveReload within a task re-run to re­quest a page re­fresh

Finally, for the magic, we’ll add a new pipe to our html, scss, and js tasks to LiveReload when these tasks are called to be run again by gulp.watch. Here is the scss task as a sam­ple:

gulp.task("scss", function() {
return sass("source/assets/**/*.scss")
.pipe(gulp.dest("build/assets/"))
.pipe(gulpLiveReload());
});

One key thing to note is that our server task only runs af­ter html, scss and js tasks have been run once; our watch task also runs only af­ter server task is run. This means that the first at­tempt by gulp-livereload to re­load the page will be fu­tile be­cause the LiveReload server is not yet lis­ten­ing for changes. This ap­proach pre­vents un­nec­es­sary re­loads on the first run of html, scss and js tasks.

Shortcomings

Each task re­quires an ad­di­tional pipe with a call to gulp-livereload for my im­ple­men­ta­tion to work. dmitriz’s im­ple­men­ta­tion works dif­fer­ently and does not ne­ces­si­tate such a be­hav­iour.

If there are any more short­com­ings in your opin­ion, please feel free to open an is­sue on the GitHub pro­ject, or sub­mit a PR.

An al­ter­nate ap­proach

Another ap­proach is to use gulp.watch and livereload.changed() like so:

gulp.task("watch", ["server"], function() {
// start the LiveReload server
gulpLiveReload.listen();
var watch1 = gulp.watch("source/**/*.html", ["html"]);
watch1.on("changed", function(event) {
gulpLiveReload.changed(event.path);
});
// gulp.watch('source/assets/**/*.scss', ['scss']),
// gulp.watch('source/assets/**/*.js', ['js']);
});

This will, how­ever, re­load the page be­fore the html task fin­ishes run­ning. To counter this, you would have to in­stall run-sequence and con­fig­ure the or­der of tasks. That’s yet an­other mod­ule we would have to add to our pro­ject. Although it would bring the en­tire re­load logic within a sin­gle watch task, it is ex­ten­sive and un­nec­es­sary in my opin­ion. The run-sequence pro­ject page says it is a tem­po­rary fix and such a fix should not be re­quired with the re­lease of Gulp 4.0. Fingers crossed.