Apr 26th 2015Compiling your ES6 command line apps to work with node.js

This is a follow-up to the "Making your io.js command line apps compatible with node.js" post. While the previous article deals with running your ES6 command line app through babel-node when on a node.js platform, this article deals with how you setup your project in a way that an ES5 version of your app is automatically compiled and used when on a node.js platform and the original ES6 version is used on an io.js platform.

This has several advantages, such as not having to require Babel in your production dependencies. Also, performance is better on ES5 platforms when the files are pre-compiled and it is more trivial to run the tests on ES5 platforms if they are also written in ES6.

Introducing our sample project

We will be using the say-hello app we built in the "Making your io.js command line apps compatible with node.js" post. You can find the version for this article on the master branch.

Directory structure

This is how I setup my ES6/ES5 hybrid command line app gitclick:

es5/
  bin/
  test/
  main.js
src/
  bin/
  test/
  main.js
node_modules/
main.js
package.json

Some observations:

Installing our hybrid dependencies

$ npm i is-iojs -S && npm i babel -D

This command will install is-iojs as a normal dependency and babel as a development dependency. We only need Babel to build the ES5-version before we publish our project to npm but our project will not require Babel at runtime.

Setting up our package.json scripts

"scripts": {
  "build": "mkdir -p es5/bin && babel src/bin/say-hello --out-file es5/bin/say-hello",
  "prepublish": "npm run build"
}

Building the app

With all the meta-stuff setup, let's build our little say-hello app. You will be able to run say-hello from your terminal to either display "Hello io.js" or "Hello node.js", depending on which platform you are on.

./src/bin/say-hello

#!/usr/bin/env node
'use strict';

const iojs = require('is-iojs');
const engine = iojs ? 'io.js' : 'node.js';

console.log(`Hello ${engine}`);

Now, let's make sure that this app runs on both platforms:

Going hybrid

Depending on our platform, we will either direct our user to the ES6-bin or the ES5-bin:

./bin/say-hello

#!/usr/bin/env node
'use strict';

require(require('is-iojs') ? '../src/bin/say-hello' : '../es5/bin/say-hello');

Don't forget to declare the path of your bin:

package.json

"bin": {
  "gitclick": "bin/gitclick"
}

And make sure that the file is executable:

$ chmod +x bin/say-hello

Done

That was pretty much it. Check out the code in the GitHub repository for this little demo.

Bonus: Testing your ES5-compiled app

I didn't implement this in the say-hello demo but this is how I test ES6 and ES5 versions for gitclick.

To test your app when compiled to ES5, you can use the following scripts:

"scripts": {
  "test-es6": "mocha src/test --recursive",
  "test-es5": "mocha es5/test --recursive",
  "test": "if [ \"$(node -e \"console.log(require('is-iojs'))\")\" = \"true\" ]; then npm run test-es6; else npm run test-es5; fi;"
}

If you use Travis CI for continuous integration, you could setup your .travis.yml like this:

language: node_js
node_js:
  - '0.10'
  - '0.11'
  - '0.12'
  - 'iojs'

This will run your tests against multiple version of node.js and the latest io.js.