BabelJS

An alternative to TypeScript is to use the BabelJS compiler. This is an open source project ECMAScript-2015 and beyond to the equivalent ECMAScript 5 JavaScript. A lot of the changes put in place for ECMAScript-2015 are syntactic niceties, so they can actually be represented in ECMAScript 5 JavaScript, although not as succinctly or as pleasantly. We've seen that already using class-like structures in ES 5. BabelJS is written in JavaScript, which means that the compilation from ECMAScript-2015 to ES 5 is possible directly on a web page. Of course, as seems to be the trend with compilers, the source code for BabelJS makes use of ES 6 constructs, so BabelJS must be used to compile BabelJS.

At the time of writing, the list of ES6 functions that are supported by BabelJS are extensive:

  • Arrow functions
  • Classes
  • Computed property names
  • Default parameters
  • Destructuring assignment
  • Iterators and for of
  • Generator comprehension
  • Generators
  • Modules
  • Numeric literals
  • Property method assignment
  • Object initializer shorthand
  • Rest parameters
  • Spread
  • Template literals
  • Promises

BabelJS is multi-purpose JavaScript compiler, so compiling ES-2015 code is simply one of the many things it can do. There are numerous plugins which provide a wide array of interesting functionality. For instance, the "Inline environmental variable" plugin inserts compile time variables, allowing for conditional compilation based on environments.

There is already a fair bit of documentation available on how each of these features work so we won't go over all of them.

Setting up Babel JS is a fairly simple exercise if you already have node and npm installed:

npm install –g babel-cli

This will create a BabelJS binary which can do compilation like so:

babel  input.js --o output.js

For most use cases you'll want to investigate using build tools such as Gulp or Grunt, which can compile many files at once and perform any number of post-compilation steps.

Classes

By now you should be getting sick of reading about different ways to make classes in JavaScript. Unfortunately for you I'm the one writing this book so let's look at one final example. We'll use the same castle example from earlier.

Modules within files are not supported in BabelJS. Instead, files are treated as modules, which allows for dynamic loading of modules in a fashion not unlike require.js. Thus we'll drop the module definition from our castle and stick to just the classes. One other feature that exists in TypeScript and not ES 6 is prefacing a parameter with public to make it a public property on a class. Instead we make use of the export directive.

Once we've made these changes, the source ES6 file looks like the following:

export class BaseStructure {
  constructor() {
    console.log("Structure built");
  }
}

export class Castle extends BaseStructure {
  constructor(name){
    this.name = name;
    super();
  }
  Build(){
    console.log("Castle built: " + this.name);
  }
}

The resulting ES 5 JavaScript looks like the following:

"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; }; }();

Object.defineProperty(exports, "__esModule", {
  value: true
});

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeofcall === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var BaseStructure = exports.BaseStructure = function BaseStructure() {
  _classCallCheck(this, BaseStructure);
  console.log("Structure built");
};

var Castle = exports.Castle = function (_BaseStructure) {
  _inherits(Castle, _BaseStructure);
  function Castle(name) {
    _classCallCheck(this, Castle);
    var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Castle).call(this));
    _this.name = name;
    return _this;
  }
  _createClass(Castle, [{
    key: "Build",
    value: function Build() {
      console.log("Castle built: " + this.name);
    }
  }]);
  return Castle;
}(BaseStructure);

Right away it is apparent that the code produced by BabelJS is not as clean as the code from TypeScript. You may also have noticed that there are some helper functions employed to handle inheritance scenarios. There are also a number of mentions of "use strict";. This is an instruction to the JavaScript engine that it should run in strict mode.

Strict mode prevents a number of dangerous JavaScript practices. For instance, in some JavaScript interpreters it is legal to use a variable without declaring it first:

x = 22;

This will throw an error if x has not previously been declared:

var x = 22;

Duplicating properties in objects is disallowed, as well as double declaring a parameter. There are a number of other practises that "use strict"; will treat as errors. I like to think of "use strict"; as being similar to treating all warnings as errors. It isn't, perhaps, as complete as –Werror in GCC but it is still a good idea to use strict mode on new JavaScript code bases. BabelJS simply enforces that for you.

Default parameters

Not a huge feature but a real nicety in ES 6 is the introduction of default parameters. It has always been possible to call a function in JavaScript without specifying all the parameters. Parameters are simply populated from left to right until there are no more values and all remaining parameters are given undefined.

Default parameters allow setting a value other than undefined for parameters that aren't filled out:

function CreateFeast(meat, drink = "wine"){
  console.log("The meat is: " + meat);
  console.log("The drink is: " + drink);
}
CreateFeast("Boar", "Beer");
CreateFeast("Venison");

This will output the following:

The meat is: Boar
The drink is: Beer
The meat is: Venison
The drink is: wine

The JavaScript code produced is actually very simple:

"use strict";
function CreateFeast(meat) {
  var drink = arguments.length <= 1 || arguments[1] === undefined ? "wine" : arguments[1];
  console.log("The meat is: " + meat);
  console.log("The drink is: " + drink);
}
CreateFeast("Boar", "Beer");
CreateFeast("Venison");

Template literals

On the surface, template literals seem to be a solution for the lack of string interpolation in JavaScript. In some languages, such as Ruby and Python, you can inject substitutions from the surrounding code directly into a string without having to pass them into some sort of string formatting function. For instance, in Ruby you can do the following:

name= "Stannis";
print "The one true king is ${name}"

This will bind the ${name} parameter to the name from the surrounding scope.

ES6 supports template literals that allow something similar in JavaScript:

var name = "Stannis";
console.log(`The one true king is ${name}`);

It may be difficult to see but that string is actually surrounded by backticks and not quotation marks. Tokens to bind to the scope are denoted by ${}. Within the braces you can put complex expressions such as:

var army1Size = 5000;
var army2Size = 3578;
console.log(`The surviving army will be ${army1Size > army2Size ? "Army 1": "Army 2"}`);

The BabelJS compiled version of this code simply substitutes appending strings for the string interpolation:

var army1Size = 5000;
var army2Size = 3578;
console.log(("The surviving army will be " + (army1Size > army2Size ? "Army 1" : "Army 2")));

Template literals also solve a number of other problems. New line characters inside of a template literal are legal, meaning that you can use template literals to create multiline strings.

With the multiline string idea in mind, it seems like template literals might be useful for building domain specific languages: a topic we've seen a number of times already. The DSL can be embedded in a template literal and then values from outside plugged in. An example might be using it to hold HTML strings (certainly a DSL) and inserting values in from a model. These could, perhaps, take the place of some of the template tools in use today.

Block bindings with let

The scoping of variables in JavaScript is weird. If you define a variable inside a block, say inside an if statement, then that variable is still available outside of the block. For example, see the following code:

if(true)
{
  var outside = 9;
}
console.log(outside);

This code will print 9, even though the variable outside is clearly out of scope. At least it is out of scope if you assume that JavaScript is like other C-syntax languages and supports block level scoping. The scoping in JavaScript is actually function level. Variables declared in code blocks like those found attached to if and for loop statements are hoisted to the beginning of the function. This means that they remain in scope for the entirety of the function.

ES 6 introduces a new keyword, let, which scopes variables to the block level. This sort of variable is ideal for use in loops or to maintain proper variable values inside an if statement. Traceur implements support for block scoped variables. However, the support is experimental at the moment due to performance implications.

Consider the following code:

if(true)
{
  var outside = 9;
  et inside = 7;
}
console.log(outside);
console.log(inside);

This will compile to the following:

var inside$__0;
if (true) {
  var outside = 9;
  inside$__0 = 7;
}
console.log(outside);
console.log(inside);

You can see that the inner variable is replaced with a renamed one. Once outside the block, the variable is no longer replaced. Running this code will report that inside is undefined when the console.log method occurs.

In production

BabelJS is a very powerful tool for replicating many of the structures and features of the next version of JavaScript today. However, the code generated is never going to be quite as efficient as having native support for the constructs. It may be worth benchmarking the generated code to ensure that it continues to meet the performance requirements of your project.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset