Livereloading in Gulp without a browser extension

Pre-requisites

Introduction

The fever over task-runners like Grunt, Gulp and Broccoli has largely died in 2017 due to superior alternatives such as Webpack. However, if you’re like me, you’re still using Gulp simply because it works for you and you’re not working on big projects or in big teams to necessitate the change to Webpack.

Since initially learning about LiveReload in 2015, I have moved to Firefox (and for the most part, appreciate it). This means I do not have access to an official addon as I did on Chrome. Not only this, I neither want to install an additional extension for something I do not do 24x7 (hobby web developer), nor bloat my Firefox installation, especially if I can help it.

The problem

If you read the article on implementing LiveReload, you must have come across the following section:

Next, we need to download the Google Chrome extension LiveReload, go to the Chrome Store and download it here. Make sure you can view it in your tool bar and that the circle is filled in with black. This is important or else it won’t work.

From dmitriz’s gulp-automation project, which inspired me to understand his code and implement my own from scratch, you can see why he argues for a different approach:

Many of them are based on installing a Chrome browser extension. However, in addition to being limited to the Chrome browser, extensions are potentially vulnerable. Extensions can also be guilty to slow down your browser, by adding unnecessary and slow running scripts blocking your useful content. As extension runs on every page in your browser, it doesn’t seem to be a good fit for the purpose of LiveReload.

Fixing our problem

For the more relevant part, let’s start discussing what we are going to do to solve the problem.

Overview

  • Run a server on Node using Gulp
  • Set up base for LiveReload to use
  • Start listening for changes
  • Call LiveReload within a task re-run to request a page refresh

Run a server on Node using Gulp

We are going to use connect as a server.

1
npm install --save-dev connect

Open your gulpfile.js, require connect and write a new task called server:

1
2
3
4
5
6
7
8
9
// 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 connect server cannot do anything. It knows to listen to port 8080 but does not know what the root of the website/app is. I’m serving a static website that outputs to build/ and so will configure it as such using the serve-static middleware for connect:

1
npm install --save-dev serve-static

Editing our gulpfile.js to reflect the new changes:

1
2
3
4
5
6
7
8
9
10
// 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 static files from build/ when requested over port 8080.

Set up base for LiveReload to use

We’ll add connect-livereload to our project. All it literally does is inject a piece of code to your files while the connect server is running. This means that your built files will not include this piece of code.

Since it is a middleware for our connect server, we’ll use it so.

Install it as usual:

1
npm install --save-dev connect-livereload

Require the module in your gulpfile.js and modify the server task to make use of it.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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 refresh your page now, you will see the following code injected before the close of your body tag:

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

connect-livereload does not provide the actual script, it merely injects the code to make it available for gulp-livereload to use, as we’ll see in the next section.

Start listening for changes

Since you’re here, you must be accustomed to gulp.watch.

Currently, this is what our watch task looks like:

1
2
3
4
5
gulp.task('watch', ['server'], function() {
  gulp.watch('source/**/*.html', ['html']);
  gulp.watch('source/assets/**/*.scss', ['scss']),
  gulp.watch('source/assets/**/*.js', ['js']);
});

We’ll install gulp-livereload and modify our gulpfile.js accordingly:

1
npm install --save-dev gulp-livereload
1
2
3
4
5
6
7
8
9
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 request a page refresh

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 sample:

1
2
3
4
5
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 after html, scss and js tasks have been run once; our watch task also runs only after server task is run. This means that the first attempt by gulp-livereload to reload the page will be futile because the LiveReload server is not yet listening for changes. This approach prevents unnecessary reloads on the first run of html, scss and js tasks.

Shortcomings

Each task requires an additional pipe with a call to gulp-livereload for my implementation to work. dmitriz’s implementation works differently and does not necessitate such a behaviour.

If there are any more shortcomings in your opinion, please feel free to open an issue on the GitHub project, or submit a PR.

An alternate approach

Another approach is to use gulp.watch and livereload.changed() like so:

1
2
3
4
5
6
7
8
9
10
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, however, reload the page before the html task finishes running. To counter this, you would have to install run-sequence and configure the order of tasks. That’s yet another module we would have to add to our project. Although it would bring the entire reload logic within a single watch task, it is extensive and unnecessary in my opinion. The run-sequence project page says it is a temporary fix and such a fix should not be required with the release of Gulp 4.0. Fingers crossed.