How to run an app that uses Babel

I’ve recently been looking into improving the Opbeat support for Babel.

I had noticed that some of our users had issues getting good metrics from their Babel compiled applications, while others didn’t seem to have any issues — so what was the difference?

Apparently, the Opbeat agent wouldn’t always hook correctly into the user’s application and therefore miss certain key metrics. It seemed to depend on how Babel was being used to run or compile the source code.

I quickly learned that there are multiple ways to run an application with Babel. So I sat out to investigate all these different ways. This would allow me to cover all edge cases and to write an Opbeat/Babel test suite to ensure 100% compatibility in the future.

This blog post is an attempt to give an overview of these different ways and how and when to use them.

Throughout the post, we’ll be using a super simple Node.js application that uses ES Modules and outputs its own source code. We’ll put it in src/app.js:

import fs from 'fs'

fs.createReadStream(__filename).pipe(process.stdout)

Run using the Babel interpreter

The simplest way of running this code is to use the babel-node command that you get when installing the babel-cli module globally:

$ babel-node src/app.js

Running this command, I get the following output:

import fs from 'fs'

fs.createReadStream(__filename).pipe(process.stdout)

Notice how the code appears to be running as if it was being executed normally using the node binary. If we were to inspect the process.argv array, we’d even see node as the first argument.

Compile first, then run

For production use, the overhead of the runtime compilation using babel-node is obviously too high. Instead, you want to pre-compile the ES6 code so that it can be run directly by the node executable.

To do this, you use the babel command that you also get by installing the babel-cli module:

$ babel src -d lib

This will compile all the files in the src directory and write the compiled versions of each file to the lib directory. To run your compiled code, now simply use the node executable:

$ node lib/app.js

Running this command, I get the following output:

'use strict';

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

_fs2.default.createReadStream(__filename).pipe(process.stdout);

This is what the compiled version of src/app.js looks like.

Runtime compiling

Another approach to run code using Babel is to use the babel-register module. This module patches the require function so it compiles ES6 code on the fly as it is required. This allows you to use your regular toolchain to execute Node.js code without having to pre-compile the source code every time you modify a line.

One way to use the babel-register module is to create a separate “bootstrapping” file (e.g. bootstrap.js):

require('babel-register')
require('./src/app')

After requiring the babel-register module, all subsequent calls to require will result in the code being compiled using Babel. So in the above example, when we require ./src/app, the src/app.js file will be compiled by Babel before being interpreted and executed by node.

Now I simply run it using the node executable:

$ node bootstrap.js

Running this command, I get the following output:

import fs from 'fs'

fs.createReadStream(__filename).pipe(process.stdout)

As you can see, this have the same effect as running the code using babel-node.

Runtime compiling without a bootstrap file

Finally, there’s a variant of the above approach that leverages the node --require argument to pre-load the babel-register module before running the src/app.js file.

This allows you to skip the bootstrap.js file and run the ES6 file directly:

$ node -r babel-register src/app.js

The output of this is the same as above.

The only downside of the node -r approach is that it’s not possible to parse any configuration options to the babel-register module. But if you want to just use its default settings then this is an easy solution.

About the author
Thomas Watson is our Node.js lead, speaker and commited open source contributor.
You can follow him on Twitter or GitHub.