Babel Setup For Beginners
Babel.js is the most popular tool for converting ECMAScript 2015 to the more cross-browser friendly ECMAScript5. I had only played with it a bit so I recently spent a weekend giving it some intense focus.
Babel was at v5.x when I started looking at it, but it's gone through some serious changes since it went to v6.0. I spent quite a bit of time trying to grasp the changes along with some other things, so I'm documenting what I learned for future reference, as well maybe help other Babel newbies.
Table of Contents
- Assumptions & Notes
- Babel: Then & Now
- File Setup
- Task Automation with
npm scripts
- The
build-js
Task - Understanding
imports
- Conclusion
Assumptions & Notes
A few assumptions:
- I'm assuming that you have Node/npm installed and know how to use npm to install packages. If not, download Node and npm and familiarize yourself with npm packages.
I'm not assuming that you know Babel but am assuming that you have a basic understanding of ES2015. Not expert level, just basic…maybe you've read a blog post or two about it. “Exploring ES6” is nice starting point for learning.
(Note: “Exploring ES6” is free thanks to the graciousness of its author: super-duper nice guy Dr. Axel Rauschmayer. But feel free to support him and buy the book.)
A few notes:
- Grab the code on GitHub.
- ECMAScript 2015 (ES2015) is sometimes referred to ECMAScript6 (ES6, like Axel's book does) or ES.next. ECMAScript 2015 is the official name so I'm going to refer to it using its common ES2015 abbreviation.
- I'm still learning about the whole ES2015/Babel/Browserify thing so there may be a better way to do all this. Feel free to leave me a comment if you think this is the case.
Babel: Then & Now
Babel is a tool for compiling ECMAScript 2015 (ES2015) down to ECMAScript5 (ES5), which has wider browser support. This is good because by compiling ES2015 to a JavaScript version with wider browser support, Babel lets us use future JavaScript functionality RIGHT NOW and not wait for browser adoption.
Up to v5.x, Babel had built-in “transforms” that compiled the code down to ES5. But v6.0 removed the transforms and, instead, converted them to external plugins that end-users can install as needed.
This made Babel more modular, which is great, but it took me time to figure out what plugins I needed to do the ES2015 conversions. Plus, I had to get everything to work with Browserify.
File Setup
We'll focus on the key files related to the post and ignore the others, but here's the file structure:
es2015-stuff
├── build
| ├── bundle.js
| └── index.html
| └── style.css
├── js-build
| ├── functions.js
| └── scripts.js
├── node_modules
├── .babelrc
├── .gitignore
└── package.json
If you downloaded these code from the Git repo, the package.json
file is already configured with packages needed for this project. Running npm install
will install all of them, but I want to point out what the packages are doing:
- to work with ES2015 code while properly compiling it down to ES5, you need to install Browserify, Babelify and Babel ES2015 presets as
devDependencies
. - after this installation we need to add this field to our
package.json
so Browserify can work properly with Babel:"browserify": { "transform": [ "babelify" ] },
- we'll also need to add code to our
.babelrc
configuration file so Babel works well with the presets:{ "presets": ["es2015"] }
- we'll install two more
devDependencies
: Nodemon to watch for changes to our.js
files and httpster to create a mock server for reviewing our updates in a browser. These packages have no effect on how our code works, they just make it easier to review the work as we move along. - finally, we'll install jQuery and list in
dependencies
since it should ship with our production app.
When we add everything else to it, the final package.json
file should look similar to this:
{
"name": "babel-es6-post",
"scripts": {
"build-js": "browserify js-build/*.js --o build/bundle.js -t [ babelify --presets [ es2015 ] ]",
"watch-js": "nodemon -e js -w js-build -x 'npm run build-js'",
"server": "./node_modules/httpster/bin/httpster -p 3000 -d ./build/",
"watch": "npm run server & npm run watch-js"
},
"browserify": {
"transform": [
"babelify"
]
},
"devDependencies": {
"babel-preset-es2015": "^6.3.13",
"babelify": "^7.2.0",
"browserify": "^13.0.0",
"httpster": "^1.0.1",
"nodemon": "1.8.1"
},
"dependencies": {
"jquery": "^2.2.0"
}
}
Task Automation with npm scripts
The scripts
field lists all the automated tasks needed for the project: it's doing the work that Grunt or Gulp would usually do. So typing npm run build-js
into the terminal would run the build-js
task.
The watch-js
task uses nodemon to watch for file changes inside js-build
. So if any of those files change, the build-js
task runs automatically.
(Note: nodemon may be TOO much for this project as it appears to be made with full-on Node apps in mind and not websites. Something like watchify may be a better fit here, but I really like nodemon right now.)
The server
task uses httpster to let the files in build/
to be accessed via a web browser at localhost:3000
. The watch
tasks runs both the server and watch commands.
This is the task I fire up at when I start working so that the both the httpster
server runs and the build-js
task immediately runs, then starts watching for changes in js-build/
.
The build-js
Task
But the most important task here is the build-js
task. It's the task that uses Babel to compile ES2015 to ES5:
"build-js": "browserify js-build/*.js --o build/bundle.js -t [ babelify --presets [ es2015 ] ]",
build-js
uses Browserify, which treats JavaScript files as separate modules…we'll discuss this in-depth shortly. It concatenates the modules/files/whatever in js-build/
to a single bundle.js
file in build/
.
All those files use ES2015, which must be converted to ES5. Here's functions.js
:
// functions.js
export let getName = ( getEl, firstName="Kai", lastName="Gittens" ) => {
let fullName = `Hello ${firstName} ${lastName}`;
getEl.html( fullName );
}
Lots of ES2015 code: the let
keyword, arrow functions with default parameters and template strings. scripts.js
uses ES2015 as well:
// scripts.js
import { getName } from './functions';
import $ from 'jquery';
const TARGET_EL = $( "#targetEl" );
getName( TARGET_EL );
We're using the import
keyword from ES2015 modules as well as const
. The build-js
task uses Babelify, which helps Browserify convert all this code down to ES5.
As mentioned, Babel used to do this with internal transforms but they were removed when v6.0 was released. Plugins help here and in this task, Babelify is using a plugin in the form of --presets
, the [ es2015 ]
preset specifically.
Understanding imports
Back to Browserify working with separate modules. They actually should be thought of as dependencies, code that other code needs to get a certain job done.
JavaScript doesn't have an internal dependency system like other languages. Node gave the language this with the help of the require()
statement:
var getName = require( './functions' ),
$ = require( 'jquery' );
But ES2015 modules give JavaScript such a system with the import
statement:
import { getName } from 'functions';
import $ from 'jquery';
Getting ES2015 modules to properly compile down to ES5 caused some problems I needed to figure out. How the compiling works seem so obvious now that I solved the problem.
You see, the import { getName } from 'functions';
line implies that there are two files in the same directory: getName.js
and functions.js
, which there are in my code. A setup like this would be fine if ES2015 modules had wide browser support, but they don't and need to be converted down to ES5.
Browserify helps in the conversion, but how it finds modules differs from ES2015. It uses Node require()
to bring in modules, and require()
has its own pattern for finding module.
When told to look for a module like functions.js
, the require()
statement will look for it as follows:
- it first looks at Node internally to see if it's an internal module, like
fs
orhttp
oros
. - if it can't find it there, it looks inside
node_modules/
next which contains modules you install on your own…browserify
,gulp
, etc. - if it can't find it there, it assumes that it's your own custom module and you'll create the proper path for
require()
to find it.
This is fine for bringing in jQuery with import $ from 'jquery';
. A Browserify build will make the code ES5-friendly by changing import
to require()
.
Since it's mapped to the dependencies
field in package.json
, it's inside of node_modules/
. The require()
statement will find it eventually.
It's different for the functions.js
module: it needs to work with Browserify to properly compile down to ES2015. So in this case, the ES2015 syntax won't work…
import { getName } from 'functions';
…but the Node syntax will…
// Note the "./" before "functions"
import { getName } from './functions';
…convert to the ES5-friendly require()
statement.
var getName = require('./functions');
Keep this in mind when going through any online ES2015 module tutorials. Few of them are clear that both Browserify and Babel are need to make them work cross-browser and need a setup like this.
Conclusion
After ES2015 was released, the team responsible for finalizing the versions (TC39) decided that there will be a new ECMAScript release every year: ECMAScript 2016 (ES7) should be released a few months after this post is published. The browsers won't be able to implement all the new features right away so if this release schedule stays, transpilers like Babel will always (ALWAYS) be needed.
If you want to keep up with JavaScript, taking time to learn all the various requirements, configurations and quirks with these transpilers is time well spent.