Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

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:

'use strict';
(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"]:

4 lines .babelrc
{
"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:

'use strict';
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.

Leave a comment!

7
Login or Register to join the conversation
Default user avatar
Default user avatar Radoje Albijanic | posted 5 years ago | edited

Hey folks,

I've had a lot trouble trying to run command for creating RepLogApp.js in dist folder on Windows. Updated node to 8.9.4 (so far latest recommended) trying to fix SyntaxError: 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:


node ./node_modules/babel-cli/bin/babel.js  web/assets/js/RepLogApp.js -o web/assets/dist/RepLogApp.js
</code >```


instead. Or use bash instead of `cmd`. Hope I saved some time for someone else on Windows :)

Cheers!
2 Reply

Hey Radoje,

Hm, so you're using the different "bin" path. I think, you can also use "./node_modules/.bin/babel" but prefix it with "node", like:


node ./node_modules/.bin/babel web/assets/js/RepLogApp.js -o web/assets/dist/RepLogApp.js

I.e. run this JS file through node, it's a known issue on Windows OS.

Anyway, thanks for sharing it with others!

Cheers!

Reply
Default user avatar
Default user avatar Radoje Albijanic | Victor | posted 5 years ago

Hey Viktor,

Tried it, didn't work in my PHPStorm console or CMD. Except when using bash. Maybe there's something wrong with my OS, heads up for Win users anyway :).

Cheers!

Reply
Peter-K Avatar
Peter-K Avatar Peter-K | posted 4 years ago

The last 2 vidoes I was not able to follow, I had different versions of npm, babel & yarn installed than you and also console was throwing many errors.
I couldnt create RepLogApp.js in disc folder but anyway even if I would be able to follow. The video was a rocket science for me and my brain of 5yrs old wouldnt get it anyway what was happening but I've got an idea/understand thoery what babel should do.
It seems that the purpose of Babel is just to take new javascript and make it compliant with older browsers.... so now All I need is actually some statement in the footer saying Optimized for Chrome 2018+ and all this will be irrelevant :D lol :D

Reply

Hey Peter,

Hm, sad to hear, probably your new version have some BC breaks, or maybe you just need to specify babel version to the one we use in this screencast.

> the purpose of Babel is just to take new javascript and make it compliant with older browsers
Exactly! So you write new JS code and keep calm knowing that your code works in old browsers :)

Hahaha, adding a note in the footer may work ;)

Cheers!

Reply
GDIBass Avatar
GDIBass Avatar GDIBass | posted 5 years ago

Was finally able to transpile an app I built a while ago. Thank you!

Reply

Yes! Good work ;)

Cheers!

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses Symfony 3. But, since this is a JavaScript tutorial, all the concepts work fine in newer versions of Symfony.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.2.*", // v3.2.14
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.1
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.4.2
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.19
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.0" // v3.2.2
    }
}
userVoice