Gruntifying a Spotify App
I promised myself that the next time I was going to do any JavaScript development I’d clean up my act and put an automated build process in place first.
Well it looks like my next JavaScript project is going to involve my Spotify App, Spotify-LibBrowser; I ran it for the first time in a while the other day and noticed some serious performance issues that need to be addressed if it’s to remain usable in new versions of Spotify.
Now, just because it’s not a typical stand-alone application doesn’t mean it shouldn’t benefit from the efficiency and code quality improvements that a good build process can help provide. Spotify Apps are created using HTML5, CSS and JavaScript, so there’s no reason why they can’t make use of many of the same tools and technologies available to more standard web applications.
Because this was my first experience with most of these tools I’ve written this post as a log of how I did it. I use JSHint to provide static code analysis to make sure the JavaScript complies with a good set of coding standards and Jasmine to add some unit tests to the code.
I used Grunt to automate these tasks, and finally, I set up a continuous integration build on Travis just to give that extra feedback and confidence that everything is running smoothly.
Setting up Grunt
Grunt runs on Node.js so, if you haven’t already got it, you’ll need to install that first. It’s really easy to install (even on Windows)!
The recommended way to get Grunt set up is to install the Grunt CLI globally, (so you can run grunt
commands from any
folder), but install the actual Grunt task runner (and specific task plug-ins) locally. This allows you to have different
projects running on different versions of the Grunt task runner and you won’t risk breaking existing projects by
installing a newer version for a new project.
Seeing as we’ve got Node.js now we might as well use the Node package manager (npm) to get grunt installed. The following command installs the Grunt CLI globally…
Ian.Reah@IANREAH ~/Spotify-LibBrowser (master)
$ npm install -g grunt-cli
Next we need to install the task runner locally, but before we do that let’s just think ahead a bit. Put yourself in the shoes of a developer coming to your project and wanting to contribute. What we don’t want to do is scare them off with a long list of instructions on getting set up…‘install version X of Y here, put Z in this folder, etc’! Well, if we take a bit of care here and with the help of npm we can make things a lot easier for them (or even for your future self when you come to set up your project on a new device).
First create a package.json
file in the root of your project. It can be pretty minimal for now. At the very least
it needs a name and a version property…
Now install the Grunt task runner locally with the following command. (Notice the --save-dev
option.)
Ian.Reah@IANREAH ~/Spotify-LibBrowser (master)
$ npm install grunt --save-dev
So two things should have happened from that command:
- The grunt task runner gets installed locally in a
node_modules
folder below the root of your project. - Your
package.json
file gets modified to include adevDependencies
object with agrunt
property. (This is why that--save-dev
option was important.)
This means that anyone coming to your project can simply clone your repository and run npm install
. The
Node package manager will read the package.json
file and install the dependencies listed there. Of course,
this implies that the package.json
file needs to be part of the repository but the big advantage of this
is that we don’t need to clog up the repository with all of the dependencies. So, go ahead and add node-modules
to your .gitignore
file (or whatever the ignore mechanism is for your type of version control) and commit
this with your package.json
file.
Don’t worry too much about that strange tilde ~
before the version number. It’s just a way of
specifying a version range in npm instead of tying it
down to a specific version. It really means: ‘get the latest version of grunt greater or equal to 0.4.2
but less than 0.5. In other words, if there’s been a point release then get it - you probably want any
bug fixes, etc, BUT if the major or minor versions increase I’m going to have to test it first to make
sure it still works so don’t update to that’.
As a quick test to check that everything has installed correctly so far, try running the grunt
command.
You should see the following error…
Ian.Reah@IANREAH ~/Spotify-LibBrowser (master)
$ grunt
A valid Gruntfile could not be found. Please see the getting started guide for
more information on how to configure grunt: http://gruntjs.com/getting-started
Fatal error: Unable to find Gruntfile.
If you see this then it means the Grunt CLI is installed okay and it found your local installation of the Grunt task runner, but then it didn’t know what to do with it because we haven’t defined any tasks for it to run yet. Let’s do that now.
JSHint: Our first Grunt task
The process for adding each task to the build is more or less the same. We’ll go through the first one in detail.
1. Install the task
The code for a task is installed as a node module. You can write your own but,
unless you’re doing something pretty obscure, there’s probably already one available.
Once you’ve found (or written) the task you want, install it locally with npm using the --save-dev
option (so it
gets added to the package.json
file).
Ian.Reah@IANREAH ~/Spotify-LibBrowser (master)
$ npm install grunt-contrib-jshint --save-dev
2. Configure the task
The configuration for all tasks is held in a Gruntfile.js
file in the root of your project. Tasks are configured by
passing in an object hash to the grunt.initConfig
function (within a module.exports
wrapper function, as shown below).
The configuration for a particular task is usually held in a property of this object hash with the same name as the task.
This is what my Gruntfile.js
looks like with the JSHint task configured:
The configuration options for the JSHint task should be fairly self-explanatory. Basically, examine all of my JavaScript files, but ignore the third-party files specified.
3. Load the task
To let Grunt know which of our npm modules we want to run as tasks we need to load them with the grunt.loadNpmTasks
function after the grunt.initConfig
call.
4. Run the task
To see a list of available tasks run grunt --help
and you’ll see them listed after the command’s usage instructions and options…
Ian.Reah@IANREAH ~/Spotify-LibBrowser (master)
$ grunt --help
...
Available tasks
jshint Validate files with JSHint. *
Tasks run in the order specified. Arguments may be passed to tasks that accept
them by using colons, like "lint:files". Tasks marked with * are "multi tasks"
and will iterate over all sub-targets if no argument is specified.
...
The JSHint task can be run with grunt jshint
.
5. Automate it
So, we’ve run the JSHint task and had all of our potentially bad JavaScript pointed out to us! Now, it’s all very well to go through fixing a couple of the issues and then running the task again, and so on. However, Grunt is all about automation and saving us from repetitive tasks, so we can do better…
With grunt-contrib-watch
we can configure it to automatically run our task whenever any of the files change. Because
grunt-contrib-watch
is a grunt task itself then we repeat the same process to set it up:
npm install grunt-contrib-watch --save-dev
to install it- Add the
watch
configuration to thegrunt.initConfig
object hash. The following configuration says, ‘whenever any JavaScript file changes, run the JSHint task’.
- Add the
grunt.loadNpmTasks
call to load the task
- Run the task with
grunt watch
This time you’ll be able to fix some JSHint warnings and whenever you save the file then the JSHint task will be run again so you can keep track of which ones are left to fix. Eventually, you’ll see something like this…
>> File "scripts\album.js" changed.
Running "jshint:files" (jshint) task
>> 5 files lint free.
Done, without errors.
Completed in 1.747s at Tue Dec 31 2013 18:11:34 GMT+0000 (GMT Standard Time)
Waiting...
And, as you continue to work on your project, the JSHint will be continuously running in the background so you’ll see immediately if you cause any more JSHint violations!
Just one more note about JSHint before we move one. While you’re fixing the warnings make sure you understand them. It’s generally a bad idea to change the code if you don’t really understand why it’s a problem in the first place and you can, in some cases, end up introducing bugs.
Unit tests with jasmine
So we’ve actually added two grunt tasks now, jshint
and watch
. Let’s just whizz through another one to make sure we’ve
got the process nailed.
1. Install the task (and add it to our package.json
as a dev dependency) - npm install grunt-contrib-jasmine --save-dev
2. Configure the task by specifying the task options in an object hash passed into the grunt.initConfig
call in our
Gruntfile.js
file
In most cases you’d want to specify all of your JavaScript files in the Jasmine src
option but for now I’m
just adding a simple test for one file. The purpose of this exercise is just to get the mechanism in place to
allow me to work in a more test-driven way from now on. I’m not aiming for 100% test coverage straight away.
The vendor
option specifies any third-party dependencies that need to be loaded first. With the helpers
option you can specify any files containing mocks or other set up for the tests. These are loaded after the
vendor
files. To run the simple test for the album.js
file I just need a mock for the Spotify API and its
exports
object…
spechelper.js:
Finally the specs
option specifies the files containing your tests. Here’s the one basic test I added for now, just to make sure it’s all
set up properly…
Check out the plug-in’s read me for more information about the configuration of the task and the Jasmine website for more details about writing Jasmine unit tests.
3. Load the task by adding a call to grunt.loadNpmTasks
to the Gruntfile.js
file
4. Run the task with grunt jasmine
. (Remember you can always run grunt --help
to check the list of available tasks and how to run them.)
5. Automate it by adding it to our watch
task configuration…
…so now we can run grunt watch
and every time we save a change to the JavaScript the jshint task will run and (assuming there are no JSHint errors)
then the unit tests will be run. Nice!
Publish it to the Spotify folder
To run the app in Spotify the files have to be in a specific folder:
- ~/Spotify (Mac OS X and Linux)
- “My Documents\Spotify” (Windows)
(Each app is in it’s own subfolder.)
Now, I always felt a bit uncomfortable working in this folder. You’ve got your whole repository in there and now we’ve added a boat load of grunt and node module files which have nothing to do with running the app in Spotify. I’d feel much better if I could keep all of this stuff away from the Spotify folder and only copy the necessary files over when I want to test the app in Spotify. Surprise, surprise - you can do this with a Grunt task. You know the drill by now…
- Install the task with
npm install grunt-contrib-copy --save-dev
- Add the task configuration to the
Gruntfile.js
file
The manifest.json
and index.html
along with any images, scripts and styles are all we need to run the app in Spotify.
Notice that we’re now taking advantage of the fact that the grunt configuration is actual JavaScript (not just JSON) by including a function to determine the
path for the Spotify folder. Also we can template values within <% %>
delimiters. The config object is the context when these templated values are resolved,
which is why we can call getSpotifyFolder
to set the value for the dest
option in the copy
task configuration. You can read more about using templates
to configure tasks in the grunt documentation.
- Load the task by adding a call to
grunt.loadNpmTasks('grunt-contrib-copy')
to theGruntfile.js
file - Run the task with
grunt copy
- Automate it by adding it to our
watch
task configuration
So running grunt watch
now will run the jshint
, jasmine
and copy
tasks whenever there are any JavaScript changes in the project. The advantage is that,
by default, the chain of tasks executed by the watch
task stops as soon as any task fails. In other words, it will only end up in the Spotify folder if there
are no JSHint errors in the JavaScript and all unit tests pass.
Make a Continuous Integration build with Travis
We’ve got all our tasks set up locally, and of course, no one would ever dream of publishing changes to the remote repository without first making sure all of the tasks run successfully! Nevertheless, it’s still important to have an automated build that does a clean checkout and runs all of the tasks whenever anything gets pushed to the remote repository. There may be some local dependencies you’d overlooked that could cause problems if somebody else tries to pull your changes, for example. An automated build catches things like this early and gives you some added confidence that all is well with your code.
For this I used Travis. It’s integrated with GitHub and it’s really easy to get started.
For a Node.js project, Travis runs npm test
to run the test suite. So, once you’ve
set up the GitHub service hook
and added your .travis.yml to your project,
you need to tell Travis what running the test suite actually means. In our case, this means telling it which Grunt tasks to run.
Use the scripts
configuration in the package.json
to tell it to run our jshint
and jasmine
tasks…
(The --verbose
option can be useful, especially if you find yourself in a ‘but it works on my machine’ situation!)
Finally, there’s just one little gotcha we need to work around to get this to work. Remember how we didn’t make the Grunt CLI a dev-dependency
as typically that’s installed globally? Well each build is deliberately run in a vanilla environment so the CLI won’t be available from scratch.
We can tell Travis to install the Grunt CLI first by using the before_install
configuration in the .travis.yml
file.
My .travis.yml
file looks like this…
Now you can grab your status badge and wear it with pride (or just stick it on your README file)…
…you’re now well on your way to cleaning up your act!
1. I really hope it's green while you're reading this!
comments powered by Disqus
About
I work as a Software Developer at Nonlinear Dynamics Limited, a developer of proteomics and metabolomics software.
My day job mainly involves developing Windows desktop applications with C# .NET.
My hobby/spare-time development tends to focus on playing around with some different technologies (which, at the minute seems to be web application development with JavaScript).
It’s this hobby/spare-time development that you’re most likely to read about here.
Ian Reah