Skip to content

Pre-compiling Node.js Modules

Automated Publishing Toolchains for Node.js Modules

I recently worked on a pretty cool feature for autocannon that makes it easier for users to download and use it. The feature is the pre-compilation of the native Node.js modules that autocannon depends on. This means that autocannon users don’t have to compile dependencies themselves. Therefore, they don’t need to have build tools installed, and this lowers the entry barrier for using Autocannon.

Autocannon itself only has a single native module that needs to be compiled, and that is  native-hdr-histogram . To do this, I needed to switch out node-gyp for  node-pre-gyp , and “publish” compiled versions of the Node.js modules on AWS S3.

Our goal was to give users running node 4, 5 & 6 the ability to download these native Node.js modules, without the need to compile them. To do this, we needed to compile the binaries for each Node.js version for Windows 64 and 32 bit, Linux 64 and 32 bit and OSx 64 bit only. That means on a build and publish, we needed to compile 15 binaries and publish them to some form of publicly available cloud storage.

CI automation to the rescue.

Prerequisites

To move forward with this work, you will need to create an Amazon S3 bucket, and have an IAM user policy with a user configured with the ability to publish to that bucket. You will also need this user’s access key and secret access key.  Read the Readme of node-pre-gyp over here  and figure it out. For this, we created a bucket called autocannon-assets, in eu-west-1. These minor details are important to note. We use the url for this bucket ( https://autocannon-assets.s3-eu-west-1.amazonaws.com ) to build sane urls to download the binaries.

The Setup

node-gyp is a build tool which is used extensively in Node.js projects. node-pre-gyp is a wrapper around this. It adds functionality to build and publish Node.js modules to AWS S3 buckets and download those published Node.js modules when running npm install. It can fall back to compiling them the way node-gyp does. However, this is only when there is no published version available on S3 which is compatible with the user’s installed node version. In this case, users will need compatible build tools to build it.

With native-hdr-histogram, we aimed to automate as much of the publishing and building process as possible. To do this, we made heavy usage of CI tooling. For Linux and OSX support, we used Travis, and to support Windows we needed to add Appveyor to the mix. To support these CI tools, we needed to have scripts created for the auto publishing of binaries. Figuring out all the bits and bobs that the scripts needed took a while, and I’m pretty sure  I killed Travis’s osx build infrastructure while working on this! To run CI tools with the ability to publish to S3 buckets, we needed to embed our keys within the repository.  This is a pretty big no no if you don’t want people getting access to your infrastructure.  To safely embed the keys in the corresponding  .travis.yml  and  appveyor.yml , you need to first encrypt them. There is a different path for encrypting these keys within each platform. To encrypt these keys on Travis you must use a Ruby gem. To install this gem, simply do the following in the command line: gem install travis After it is installed, you then need to encrypt the keys with the following commands. (This is best done in the directory of the project; otherwise you must add -r owner/project to the command)

Plain Text
travis encrypt node_pre_gyp_accessKeyId=${node_pre_gyp_accessKeyId} travis encrypt node_pre_gyp_secretAccessKey=${node_pre_gyp_secretAccessKey}

This will then output something like secure: “…….. encrypted ………” which can be put into your .travis.yml like so:

Plain Text
env:
  global:
     - secure: xb95WkJu5sab……………
     - secure: wugo4xhy7hO5SErSxy……………

On Appveyor, the project owner needs to be the one to encrypt the keys. To do this, you must be signed in as the project owner, and navigate to the  encrypt data page . On this page, you simple copy and paste your access key and secret access key into the box and get the results. To then use these keys, embed them into your appveyor.yml like so:

Plain Text
environment:
  node_pre_gyp_accessKeyId:
    secure: BB4XzOLXvG+XMmXyCVwqaFF/hKJnYvjIACQMD423QvY=
  node_pre_gyp_secretAccessKey:
    secure: BFW25HQ0Jdq8IpysC59Y2mh1J9IsXwWB+FTElHVSYVl5xDwwZPHrXwctZQLzKrw9

You can read the readme of node-pre-gyp for  travis  and  appveyor  if you want to know more.

We didn’t always want to publish binaries when they were run through CI though, so we needed to put a safeguard in place. That was to only publish module binaries when the the commit message contained [publish binary]. We prepend version bumps with this, to trigger building and publishing in the CI server. eg, [publish binary] 0.3.0 would cause a publish. There is already an existing module which essentially has scripts which does everything that we needed, which is the  node-sqlite3 module  by  mapbox . This module had many scripts which I was able to repurpose for our uses under the BSD license.

Successful build and publish in Travis and Appveyor with a commit tagged with “[publish binary]“.

Basically, if you want do something similar, take the /scripts/ folder from  native-hdr-histogram , along with the Makefile, and copy and modify the appveyor.yml and .travis.yml files as you need. It looks scarier than it is, they’re already configured to handle node 4-6 for you. You might need to change the test target in the Makefile depending on your setup.

You also need to modify your package.json, and add a  binary attribute . The module_path, remote_path, and package_name can be copied as is, but the host must be changed to match your S3 bucket URL. The module_name is the name of the compiled binary, I named it “histogram” for simplicity.

Additionally, using node-gyp requires you to create a binding.gyp file. When using node-pre-gyp, you must modify this file to include a “target” with the exact same content as this  one , and you should modify your  normal build target  to use the module_name value used in your package.json, by setting its “target_name” to “<(module_name)”.

Your index.js, or rather, the main file that you export from your package.json should require the native file for exporting, using node-pre-gyp. It should end up similar to the one from  here , assuming your binary section was similar to mine.

Now, when you want to publish a binary, all you need to do is add [publish binary] to your commit and push it to Github. It will take a while, because appveyor is kinda  ever so slightly  slow, but in the meantime if a user does download your native module without the published version available, they will simply build it themselves locally.

Conclusion

So, I’m sorry this was super long, technical, and didn’t have many examples, but it is one of the cooler technical things I have worked on recently that I wanted to share with people. Using this, we can create really nice automated publishing toolchains for any native Node.js modules and isn’t that what everyone wants?

How to keep developers happy; passing builds in their CI.

You may find these articles about Node.js modules helpful as well.

Insight, imagination and expertly engineered solutions to accelerate and sustain progress.

Contact