Using Grunt For Live Reload
Recently I was working on a JavaScript project that required a lot of trial and error coding which resulted in me hitting the F5 key repeatedly to see if things were working.
Grunt has been on my radar for awhile and I knew it could ‘watch’ things so I figured I could use it to reload my pages for me as I changed them.
You need a few things to get up and running here - I won’t cover installation of everything - it’s fairly straightforward, but for this example you will need:
- Node.js - does stuff with JavaScript
- Grunt - the JavaScript task runner
- NPM - Node’s package manager (like apt-get for Node)
Everything we’ll be doing runs on the command line so if you are on Windows I’d also install a good terminal like ConEmu which I’ve blogged about before.
A rough outline of what we’ll do:
- Create a new directory for our files
- Create a ‘package’ file that tells NPM what to install
- Run NPM. NPM will read our package file and fetch the things it needs
- Go get a drink while it downloads everything
- Create a file that tells Grunt what to do
- Run that Grunt file
- Curse when it doesn’t work
- Tweak, test and rejoice when it does
Directory Setup
For this example I setup a simple ’test’ folder in my webroot:
:::console
C:\inetpub\wwwroot\railo\test
And in that directory create your index.html file.
Package.JSON
Open your favorite text editor, paste the following and save as package.json.
:::json
{
"name": "grunt-reload",
"version": "1.0.0",
"devDependencies": {
"grunt": "~0.4.3",
"matchdep": "~0.3.0",
"grunt-express": "~1.2.1",
"grunt-contrib-watch": "~0.6.0",
"grunt-open": "~0.2.3"
}
}
Name and version can be anything but they are required. devDependencies is a list of Grunt plugins we need for our project.
Don’t worry too much about the versions listed for each plugin, these should be fairly current, and it’s possible to automatically update them with NPM (maybe in another blog post).
NPM
Open your console, navigate to the new project directory and run:
:::console
npm install
NPM install will look at your package.json file and install all the listed packages AND any dependencies they may have.
Sit back because it takes a minute or two.
Gruntfile.js
In the same root directory as your package.json file create a file names Gruntfile.js.
:::javascript
module.exports = function(grunt) {
// Load Grunt tasks declared in the package.json file
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
// Configure Grunt
grunt.initConfig({
// Grunt express - our webserver
// https://github.com/blai/grunt-express
express: {
all: {
options: {
bases: ['C:\\inetpub\\wwwroot\\test'],
port: 8080,
hostname: "0.0.0.0",
livereload: true
}
}
},
// grunt-watch will monitor the projects files
// https://github.com/gruntjs/grunt-contrib-watch
watch: {
all: {
files: '**/*.html',
options: {
livereload: true
}
}
},
// grunt-open will open your browser at the project's URL
// https://www.npmjs.org/package/grunt-open
open: {
all: {
path: 'http://localhost:8080/index.html'
}
}
});
// Creates the `server` task
grunt.registerTask('server', [
'express',
'open',
'watch'
]);
};
Under The Hood
Stepping through the Gruntfile.js, all grunt files must have:
:::javascript
module.exports = function(grunt) {
... grunt config, etc...
};
Next we examine our package.json file for the required plugins:
:::javascript
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
Then we do our general Grunt config - all Grunt files need this:
grunt.initConfig({
...
});
Next we configure our Grunt plugins.
First is grunt-express which will provide a lightweight web server using Express.js:
:::javascript
express: {
all: {
options: {
bases: ['C:\\inetpub\\wwwroot\\test'],
port: 8080,
hostname: "0.0.0.0",
livereload: true
}
}
},
Bases denotes where it will look for files (note the weird path syntax for Windows). You can also define a port and hostname. I’m using 8080 as I know it doesn’t conflict with anything else and we’ll simply use ’localhost’ for testing. Livereload is one of the hooks that will reload our page:
This options allows you to define the livereload port (or if you set it to true, it will use the default livereload port: 35729), and when you also define bases options, then the livereload server will be watching all contents under your bases folder, and perform livereload when those contents change.
Next is grunt-watch:
:::javascript
watch: {
all: {
files: '**/*.html',
options: {
livereload: true
}
}
},
This simply tells grunt what to ‘watch’. You could also add ’tasks’ in here to run JSHint for example when you file(s) changed. Notice it also has a ’livereload’.
And finally grunt-open will open a browser window with your file loaded when you start the script.
Note: use the same port you specied in the Express config above.
:::javascript
open: {
all: {
path: 'http://localhost:8080/index.html'
}
}
RegisterTask simply tells Grunt what to do when we run the file:
:::javascript
grunt.registerTask('server', [
'express',
'open',
'watch'
]);
Open a terminal, navigate to your directory and run ‘grunt server’. Our express, open and watch tasks should now run.
:::console
[C:\inetpub\wwwroot\test]grunt server
Running "express:all" (express) task
Running "express-server:all" (express-server) task
Web server started on port:8080, hostname: 0.0.0.0 [pid: 4724]
Running "open:all" (open) task
Running "watch" task
Waiting...
Troubleshooting
If your browser opens and you get
:::console
Cannot GET /index.html
You need to adjust your ‘bases’ path in the Express config.
On my work computer (Win7/32bit) I get really high CPU usage (90+%) but on at home (Win8/64bit) I only see 50% at most. Still digging into that issue. Grunt-contrib-watch has some posts about high CPU usage so that may be it.
Conclusion
Once you fire off ‘grunt server’ your browser should open to your index.html page and if you edit the page and save you should see a message in the console:
:::console
Running "watch" task
Waiting...
File "index.html" changed.
Completed in 0.001s at Thu Mar 13 2014 22:12:17 GMT-0400 (Eastern Daylight Time) - Waiting...
And your page should automatically reload.
Rejoice!