babel-node Tutorial
Greetings, friends! Happy Thanksgiving to all who celebrate it! 🦃 In my last article, I talked about the ts-node package that lets you run a TypeScript REPL for Node.js. In this article, I will talk about babel-node which behaves very similar to ts-node except it transpiles code using Babel instead of TypeScript. For those interested in learning more about Babel, please visit Part 1 of my Babel series.
Executing JavaScript Files with Babel
The babel-node CLI works just like the Node.js CLI, with the additional benefit of transpiling your code with Babel presets and plugins before executing it. Let's experiment with it!
First, we'll create a new directory and navigate to it:
mkdir babel-node-tutorial && cd $_
Then, we'll bootstrap a new project with a package.json file:
npm init -y
To use babel-node, we need to install it along with the core Babel package.
npm i @babel/core @babel/node
Next, we will install the @babel/preset-env and @babel/preset-react packages. We'll be experimenting with an example that uses babel-node to transpile JSX to React code, so we'll need to install React as well.
npm i @babel/preset-env @babel/preset-react react
Alright. Lets create a few files to test out what babel-node can do. Suppose we had a script named fruits.js
that simply exported a list of fruits.
export const fruits = ['apple', 'orange', 'banana', 'mango', 'plum']
We will then use an ES6 import inside a file named main.js
:
import { fruits } from './fruits'
console.log(fruits)
If we try to run node main
in our terminal, we'll get a failure due to the ES6 syntax for the import/export statements. Using ES6 import/export in Node.js is kinda awkward... Maybe it'll get better in the future or maybe Deno will replace Node.js 🤷. The latest version of Node.js that has been published is version 15.3 as of the time of this writing. Between versions 8 through 12 of Node.js, there was an experimental --experimental-modules
flag that could let you use ES6 import
instead of require
. In versions 13.2 and higher, this experimental flag is no longer needed, but additional steps are still required. You may also need to save your JavaScript file with the .mjs
extension for ES6 import/export statements to work.
You can avoid this awkwardness by using babel-node to convert your code to CommonJS syntax, so Node.js understands it. Let's create a babel.config.json
file with the following contents:
{
"presets": [
[
"@babel/preset-env",
{
"modules": "commonjs"
}
]
]
}
When we run babel-node in our terminal, it will try to pick up a Babel config in the directory that you're running it in. The path to your Babel config file can be changed using the --config-file [path]
option. You can find more options that can be passed into babel-node here.
Let's run babel-node on our main.js
file:
npx babel-node main
You should get the following output:
[ 'apple', 'orange', 'banana', 'mango', 'plum' ]
Notice that I'm using npx with babel-node. The npx command lets you run a package through the terminal as if it was installed globally. The npx command makes it much easier to run CLI programs without having to install multiple packages globally on our computer. Since we have installed @babel/core, @babel/node, and the Babel presets locally, npx will look inside our node_modules
directory for these packages. If we didn't use npx, we'd either have to install all these packages globally or create scripts inside our package.json
file, which can get tedious for experimenting with CLI programs.
We saw an example of using babel-node with @babel/preset-env. Now. let's look at an example using @babel/preset-react. We'll add this preset to our babel.config.json
file:
{
"presets": [
[
"@babel/preset-env",
{
"modules": "commonjs"
}
],
"@babel/preset-react"
]
}
Then, we'll create a file called fruitList.jsx
.
const React = require('react')
function Fruit() {
return (
<div class="fruit">
<ul>
<li>apple</li>
<li>orange</li>
<li>banana</li>
<li>mango</li>
<li>plum</li>
</ul>
</div>
)
}
console.log(Fruit())
We can use babel-node to transpile the JSX to React code and then execute it in a Node.js environment:
npx babel-node fruitList
We will get a strange looking output, but that's okay. It lets us know that our code was able to run successfully with no errors and proves that babel-node can transpile JSX. This output represents an object used internally by React when rendering components.
{
'$$typeof': Symbol(react.element),
type: 'div',
key: null,
ref: null,
props: {
class: 'fruit',
children: {
'$$typeof': Symbol(react.element),
type: 'ul',
key: null,
ref: null,
props: [Object],
_owner: null,
_store: {}
}
},
_owner: null,
_store: {}
}
Using the babel-node REPL
Babel-node also comes with a REPL similar to ts-node. This REPL lets you run a Node.js environment that transpiles ES6 code before running it.
There is one minor flaw to using babel-node. According to the documentation, ES6-style module loading is not fully supported. This means you won't be able to use the import
statement inside the babel-node REPL. However, you can still use require
statements on JavaScript code that uses ES6 exports as long as you're using presets or plugins to transform it properly like @babel/preset-env.
For example, suppose we still had our fruits.js
file from before:
export const fruits = ['apple', 'orange', 'banana', 'mango', 'plum']
We can use babel-node to require this file without any errors.
If we tried using the normal Node.js repo to require fruits.js
, we'll get a syntax error:
Conclusion
The babel-node CLI tool is great for playing around with Babel inside the Node.js runtime. It lets you transpile code using Babel presets and plugins before executing your scripts using Node.js. If you want to use Babel to build applications and generate output files, then you should use the main Babel CLI instead. If you want to learn more about babel-node, please check out the documentation.