Max Schmitt

April 26 2015

Compiling 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:

  • ./main.js is the main entry point. Here, we either load ./src/main or ./es5/main
  • ./es5/ is an ES5-mirror of ./src/ and will be built with Babel
  • node_modules and package.json live at the project's root

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

JSON

"scripts": {
"build": "mkdir -p es5/bin && babel src/bin/say-hello --out-file es5/bin/say-hello",
"prepublish": "npm run build"
}
  • build is the script that actually compiles our app down to ES5
    • babel src --out-dir es5 would compile everything in our src directory
    • The previous command skips files without the js extension, so we have to run babel src/bin/say-hello --out-file es5/bin/say-hello to compile our bin
    • Before all that, we run mkdir -p to make sure the directory exists
  • prepublish makes sure that this build is always run before we publish our package to npm

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

JS

#!/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

JS

#!/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

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:

JSON

"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:

YAML

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.