Babel: Transpiling to Old JavaScript
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
Time to use Babel! How? At your terminal, type
./node_modules/.bin/babel
Tip
On some systems, you may need to type:
node ./node_modules/.bin/babel
That is the path to the executable for Babel. Next, point to our source file:
web/assets/js/RepLogApp.js
and then pass -o
and the path to where the final,
compiled, output file should live: web/assets/dist/RepLogApp.js
.
Before you run that, go into web/assets
, and create that new dist/
directory.
Now, hold your breath and... run that command!
./node_modules/.bin/babel web/assets/js/RepLogApp.js -o web/assets/dist/RepLogApp.js
And boom! Suddenly, we have a new RepLogApp.js
file.
Before we look at it, go into index.html.twig
and update the script
tag to
point to the new dist
version of RepLogApp.js
that Babel just created:
// ... lines 1 - 53 | |
{% block javascripts %} | |
// ... lines 55 - 57 | |
<script src="{{ asset('assets/dist/RepLogApp.js') }}"></script> | |
// ... lines 59 - 65 | |
{% endblock %} |
Ok, refresh! It still works!
So what did Babel do? What are the differences between those two files? Let's find out! Open the new file:
; | |
(function (window, $, Routing, swal) { | |
let HelperInstances = new WeakMap(); | |
class RepLogApp { | |
constructor($wrapper) { | |
this.$wrapper = $wrapper; | |
this.repLogs = new Set(); | |
HelperInstances.set(this, new Helper(this.repLogs)); | |
this.loadRepLogs(); | |
this.$wrapper.on('click', '.js-delete-rep-log', this.handleRepLogDelete.bind(this)); | |
this.$wrapper.on('click', 'tbody tr', this.handleRowClick.bind(this)); | |
this.$wrapper.on('submit', RepLogApp._selectors.newRepForm, this.handleNewFormSubmit.bind(this)); | |
} | |
// ... lines 20 - 165 | |
} | |
/** | |
* A "private" object | |
*/ | |
class Helper { | |
constructor(repLogSet) { | |
this.repLogSet = repLogSet; | |
} | |
// ... lines 175 - 197 | |
} | |
const rowTemplate = repLog => ` | |
<tr data-weight="${repLog.totalWeightLifted}"> | |
<td>${repLog.itemLabel}</td> | |
<td>${repLog.reps}</td> | |
<td>${repLog.totalWeightLifted}</td> | |
<td> | |
<a href="#" | |
class="js-delete-rep-log" | |
data-url="${repLog.links._self}" | |
> | |
<span class="fa fa-trash"></span> | |
</a> | |
</td> | |
</tr> | |
`; | |
window.RepLogApp = RepLogApp; | |
})(window, jQuery, Routing, swal); |
Hmm, it actually doesn't look any different. And, that's right! To prove it, use the
diff
utility to compare the files:
diff -u web/assets/js/RepLogApp.js web/assets/dist/RepLogApp.js
Wait, so there are some differences... but they're superficial: just a few space differences here and there. Babel did not actually convert the code to the old JavaScript format! We can still see the arrow functions!
Here's the reason. As crazy as it sounds, by default, Babel does... nothing! Babel is called a transpiler, which other than being a cool word, means that it reads source code and converts it to other source code. In this case, it parses JavaScript, makes some changes to it, and outputs JavaScript. Except that... out-of-the-box, Babel doesn't actually make any changes!
Adding babel-preset-env
We need a little bit of configuration to tell Babel to do the ES2015 to ES5 transformation. In other words, to turn our new JavaScript into old JavaScript.
And they mention it right on the installation page! At the bottom, they tell you
that you probably need something called babel-preset-env
. In Babel language, a
preset is a transformation. If we want Babel to make the ES2015 transformation,
we need to install a preset that does that. The env
preset is one that does that.
And there are other presets, like CoffeeScript, ActionScript and one for ReactJS
that we'll cover in the future!
Let's install the preset with yarn:
yarn add babel-preset-env --dev
Perfect! To tell Babel to use that preset, at the root of the project, create a
.babelrc
file. Babel will automatically read this configuration file, as long
as we execute Babel from this directory. Inside, add "presets": ["env"]
:
{ | |
"presets": ["env"] | |
} |
This comes straight from the docs. And... we're done!
Try the command again! Run that diff command now:
./node_modules/.bin/babel web/assets/js/RepLogApp.js -o web/assets/dist/RepLogApp.js
diff -u web/assets/js/RepLogApp.js web/assets/dist/RepLogApp.js
Woh! Now there are big differences! In fact, it looks like almost every line
changed. Let's go look at the new RepLogApp.js
file in dist/
- it's really interesting.
Cool! First, Babel adds a few utility functions at the top:
; | |
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
(function (window, $, Routing, swal) { | |
// ... lines 8 - 335 | |
})(window, jQuery, Routing, swal); |
Below, instead of using the new class syntax, it calls one of those functions -
_createClass()
- which helps to mimic that new functionality:
// ... lines 1 - 6 | |
(function (window, $, Routing, swal) { | |
// ... lines 8 - 10 | |
var RepLogApp = function () { | |
function RepLogApp($wrapper) { | |
_classCallCheck(this, RepLogApp); | |
this.$wrapper = $wrapper; | |
this.repLogs = new Set(); | |
HelperInstances.set(this, new Helper(this.repLogs)); | |
this.loadRepLogs(); | |
this.$wrapper.on('click', '.js-delete-rep-log', this.handleRepLogDelete.bind(this)); | |
this.$wrapper.on('click', 'tbody tr', this.handleRowClick.bind(this)); | |
this.$wrapper.on('submit', RepLogApp._selectors.newRepForm, this.handleNewFormSubmit.bind(this)); | |
} | |
/** | |
* Call like this.selectors | |
*/ | |
_createClass(RepLogApp, [{ | |
key: 'loadRepLogs', | |
value: function loadRepLogs() { | |
var _this = this; | |
$.ajax({ | |
url: Routing.generate('rep_log_list') | |
}).then(function (data) { | |
var _iteratorNormalCompletion = true; | |
var _didIteratorError = false; | |
var _iteratorError = undefined; | |
try { | |
for (var _iterator = data.items[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | |
var repLog = _step.value; | |
_this._addRow(repLog); | |
} | |
} catch (err) { | |
_didIteratorError = true; | |
_iteratorError = err; | |
} finally { | |
try { | |
if (!_iteratorNormalCompletion && _iterator.return) { | |
_iterator.return(); | |
} | |
} finally { | |
if (_didIteratorError) { | |
throw _iteratorError; | |
} | |
} | |
} | |
}); | |
} | |
// ... lines 66 - 252 | |
}], [{ | |
key: '_selectors', | |
get: function get() { | |
return { | |
newRepForm: '.js-new-rep-log-form' | |
}; | |
} | |
}]); | |
return RepLogApp; | |
}(); | |
// ... lines 264 - 335 | |
})(window, jQuery, Routing, swal); |
Our arrow functions are also gone, replaced with classic anonymous functions.
There's a lot of cool, but complex stuff happening here. And fortunately, we don't need to worry about any of this! It just works! Now, even an older browser can enjoy our awesome, new code.
Tip
The purpose of the babel-preset-env
is for you to configure exactly what versions
of what browsers you need to support. It then takes care of converting everything
necessary for those browsers.
Babel and the Polyfill
But wait... it did not change our WeakMap
!
// ... lines 1 - 6 | |
(function (window, $, Routing, swal) { | |
var HelperInstances = new WeakMap(); | |
// ... lines 10 - 335 | |
})(window, jQuery, Routing, swal); |
But... isn't that only available in ES2015? Yep! Babel's job is to convert all
the new language constructs and syntaxes to the old version. But if there are
new objects or functions, it leaves those. Instead, you should use something called
a polyfill. Specifically, babel-polyfill
. This is another JavaScript library that
adds missing functionality, like WeakMap
, if it doesn't exist in whatever browser
is running our code.
We actually did something just like this in the first episode. Remember when we
were playing with the Promise
object? Guess what? That object is only available
in ES2015. To prevent browser issues, we used a polyfill.
To use this Polyfill correctly, we need to go a little bit further and learn about Webpack. That's the topic of our next tutorial... where we're going to take a huge step forward with how we write JavaScript. With webpack, we'll be able to do cool stuff like importing JavaScript files from inside of each other:
// actually imports code from helper.js!
import myHelperFunctions from './helper';
myHelperFunctions.now();
Heck, you can even import CSS from inside of JavaScript. It's bananas.
Ok guys! I hope you learned tons about ES2015/ES6/Harmony/Larry! You can already start using it by using Babel. Or, if your users all have brand-new browsers, then lucky you!
All right guys, I'll seeya next time.
Hey folks,
I've had a lot trouble trying to run command for creating
RepLogApp.js
in dist folder on Windows. Updated node to8.9.4
(so far latest recommended) trying to fixSyntaxError: missing ) after argument list
, googled and googled and din't find what it is. So after a while found some similar problem on some other topic and found the solution: run: