Max Schmitt

April 25 2015

Making your io.js command line apps compatible with node.js

If you've begun to embrace all the new features you get to use with io.js, your app will probably not run on latest node.js (0.12). const and let are only available when running node with the --harmony flag and template strings don't work at all.

I had this problem recently with a little tool I built, gitclick. I was coding away under io.js and only after I released it to the public did I realise that it is not compatible with node.js. At first, I dismissed the issue and proposed everyone to upgrade to io.js. A day later I must say that it was pretty naive of me to think that anybody would upgrade just to use my tool.

I did not want to rewrite my entire app to work with ES5 so I decided to come up with a different solution: running my app through Babel, but only if the user is not on io.js (to retain the best performance there).

A sample application: say-hello

It really is simple to get this setup, but I created a little demo project anyway. Here is how it will work:

  • Type say-hello
  • If io.js is running, you will get Hello io.js
  • If node.js is running, you will get Hello node.js

Check out the GitHub repository for quick access to the "solution".

Step 1: Create your package.json and install dependencies

package.json

JSON

{
"name": "say-hello"
}

Terminal:

$ npm i is-iojs -S

Step 2: Write the app

bin/say-hello

JS

#!/usr/bin/env node
'use strict'
const iojs = require('is-iojs')
const engine = iojs ? 'io.js' : 'node.js'
console.log(`Hello ${engine}`)

Step 3: Create a node.js-compatible wrapper

Above code would not run on the latest version of node.js because it is using features of ES6 that aren't supported there yet. This is why, we will use Babel to compile the code down to ES5 before running it on node.js. So first, install Babel as well:

Terminal:

$ npm i babel -S

Next, create a wrapper-file that runs our app through Babel if we are on a node.js platform.

bin/say-hello-harmony

JS

#!/usr/bin/env node
'use strict'
var iojs = require('is-iojs')
if (!iojs) {
require('babel/register')({ ignore: /say-hello\/node_modules/ })
}
require('./say-hello')

There is an interesting little thing about this line and the ignore option:

JS

require('babel/register')({ ignore: /say-hello\/node_modules/ })

Babel ignores files within any node_modules folder by default. If you npm install a module globally, it usually goes into a directory like /usr/local/lib/node_modules. Therefore, without the above line, Babel would not register properly because your module itself is inside a node_modules folder when installed globally.

It took me a few hours to figure out why require('babel/register') would work if I npm link a module but not if I'm installing it globally via npm. Now I know and you do too! :)

Step 4: Final steps

Now we're almost done. Just simply make sure that correct bin-file is specified in your package.json.

package.json

JSON

{
"name": "say-hello",
"bin": "bin/say-hello-harmony",
"dependencies": {
"babel": "^5.1.10",
"is-iojs": "^1.1.0"
}
}

Next, enter the following command to make the say-hello command available in your terminal.

Terminal:

$ npm link

That was it. If you're using a version manager like n (n is awesome!), you can easily test if everything works:

$ node -v
v0.12.2
$ say-hello
Hello node.js
$ n io latest
$ node -v
v1.7.1
$ say-hello
Hello io.js