Faster Node.js builds with Travis CI

Speed up the building process of your Node.js project

Martino Fornasa
clevertechbiz

--

There are a lot of tutorials about using Travis CI to build a Node.js project. However, when I started using Travis for our projects, what I really wanted to read about was performance issues, that is, how to achieve a fast build.

This post details the best practices you can use to speed up the building process. In particular, you are able to move faster by using new Travis’ container-based infrastructure and by caching the npm dependencies. It is possible to cut your build time by more than half, using these techniques.

The basics: Project boilerplate + Github repo

We start by creating a boilerplate Node.js project, and by creating a Github repository for it. In this tutorial we will be using Eskimo, a Node.js boilerplate. However, this approach is valid for every Node.js application so, feel free to skip this part if you already have a Node.js project on a Github repository.

The following will create an application boilerplate inside directory “test”.

sudo npm install -g eskimo
sudo npm install -g gulp
eskimo create test
cd test
npm install
gulp postinstall

You can test the application by running “node app” and visiting “http://localhost:3000".

Now we can create a Github repository using GitHub web interface and commit our project to the repository.

git init
git add .
git commit -m "first commit"
git remote add origin $YOUR_REPO_URL_HERE$
git push -u origin master

Your First Build

Travis CI, like several other continuous integration tools, is free for Open Source projects and it provides several plans for Private repos, on free trial for a limited period of time.

First, you need to sign in on Travis and activate the GitHub webook, as explained here. This will enable you to trigger a build each time you push on your repository or you create a Pull Request.

Second, create a “.travis.yml” file inside your project root directory with the following contents:

language: node_js
node_js:
- "0.10"
services:
- redis-server
- mongodb

This will make the build against a recent stable Node release. By default, Travis CI will run “npm install” to install dependencies and “npm test” to run the test suite. Our boilerplate already adhere to such conventions, so we are done. We also ask Travis to startup Redis and Mongo services, as we need them for testing.

Finally, add your “.travis.yml” and push to the repo:

git add .travis.yml
git commit -m "Travis build"
git push

This will start the Travis continuous integration build. On the web interface you can see the real time log and the status, which will go from started:

I measured a build process bootstrap time of 28 seconds, plus an actual running time of 2 minutes 12 seconds. Total: 2 minutes 40 seconds. Not bad, but we can do better.

Speed up (1): Using the new build infrastructure

Travis recently switched to a faster build system based on Docker containers and container based infrastructure. The main limitation in the new approach is that you can’t use “sudo”. This is not an issue if you don’t need to install OS additional packages. So, we add the following line to our “.travis.yml”:

Triggering a new build (add, commit, push), we obtain a 6 seconds bootstrap time, plus 1 min 56 sec run. Total: 2 minutes 2 seconds.

Speed up (2): Caching dependencies

Build logs show that a large amount of the running time is taken by dependency install. We can use the Travis caching to avoid such a lengthy phase.

So, we simply add the following lines to “.travis.yml”, asking Travis CI to cache the npm “node_modules” directory:

cache:
directories:
- node_modules

We obviously need two build runs to see the performance improvement, as only the second one will start to benefit from npm caching. (Please note that you can repeat the same build by using the restart button on the top-right of the interface).

This allow us to obtain a total time of 1 minute 5 seconds. That’s good!

Freezing your dependencies

During the project development phase, it could be ok to let npm fetch the latest versions of node libraries. However, maintaining this approach when the project is at production stage can be a problem, as a changing library could introduce bugs and unexpected behaviour. This also breaks build reproducibility: we can obtain a different byproduct without changing the source code.

For this reason, on later stages of development it could be useful to use “npm shrinkwrap”, which allows freezing the node dependency exact versions. Don’t forget to add the generated “npm-shrinkwrap.json” to git. To be on the safe side, I also suggest to perform a clean build by deleting cache (on the web interface, Settings -> Delete all repository caches), in order to make sure that we get rid of all the npm artifacts.

Originally published at blog.clevertech.biz.

--

--