Wednesday 19 September 2018

Introduction to Babel

What is Babel?

  • tool for transpiling (compiling) ES6/ES7 code to ECMAScript 5 code, which can be used today in any modern browser [source]
  • has extensions for transpiling JSX for React and Flow syntax for static type checking. [source]
  • babeljs.io: What is Babel?

What is a Transpiler?

Transpiler is a type of compiler that takes the source code of a program written in one programming language as its input and produces the equivalent source code in another programming language. (Wikipedia)

Why do we need Babel?

  • Allows us to write future JavaScript syntax today [source]
  • Current browsers don’t support all the new ECMAScript 6 (aka ECMAScript 2015) features yet (see compatibility table). You need to use a compiler (transpiler) to transform your ECMAScript 6 code to ECMAScript 5 compatible code. Although there are other options, Babel has become the de-facto standard to compile ECMAScript 6 applications to a version of ECMAScript that can run in current browsers. Babel can also compile other versions of ECMAScript as well as React’s JSX. [source]
  • Why Do We Need Babel (Youtube video)

How does it work?


It analyzes code and applies any of the transformation rules that are defined in its configuration and installed. 


Transformers


A transformer is a module with a specific goal that is run against your code to transform it. For example, the es6.arrowFunctions transformer has the very specific goal of transforming ES6 Arrow Functions to the equivalent ES3. This allows transformers to be completely unaware of the other transformations happening so that you can easily chain them together. [source]

Transformations come in the form of plugins.[source]

Presets


Babel can be configured to transpile specific JavaScript features. You can add much more plugins, but you can’t add to the configuration features one by one, it’s not practical. This is why Babel offers presets. [source] Presets define sets of plugins. They are collections of pre-configured plugins.

See the full list of available presets here.

If preset includes a single plugin, its name does not have to match the plugin's name e.g. preset @babel/preset-typescript includes @babel/plugin-transform-typescript plugin.

env preset


The env preset is very popular as you tell it which environments you want to support, and it does everything for you, supporting all modern JavaScript features. It includes all plugins to support modern JavaScript (ES2015, ES2016, etc.). [source]

Configuration


Babel has to be configured in order to know what it has to do. Configuration file comprises of explicitly listed:
  • presets - predefined sets of plugins
  • plugins - manually added plugins
If we want to use es2015 preset (which transpiles ES6 code to ES5) and e.g. React preset we'll install both plugins first:

> npm install babel-preset-es2015 --save-dev
> npm install babel-preset-react --save-dev

...and then list presets in Babel configuration file (.babelrc):

{
   "presets": [
      "es2015",
      "react"
   ],
   "plugins": []
}

Obviously, if we uninstall some preset/package, we have to remove it from .babelrc.

Babel modules (packages)

All Babel modules/packages (libraries, plugins and presets) are scoped under @babel name. Just like any other npm package, they can be installed either as a development or production dependencies.

@babel/cli (babel-cli)
Babel command line - a tool for transpiling the code through the command line.
To transpile ES6/ES7 file into ECMAScript 5 file run: 
babel input.js --out-file compiled.js 
[@babel/cli]

@babel/core (babel-core)
Babel core functionality. It can be used directly in JS code via require.

@babel/preset-env
Preset that allows using the latest JavaScript without needing to micromanage which syntax transforms and (optionally) browser polyfills are needed by target environment(s). It replaces many presets that were previously used including: babel-preset-es2015 (transpiles ECMAScript 2015/ES2015/ES6 code into ES5 [source]), babel-preset-es2016, babel-preset-es2017, babel-preset-latest, babel-preset-node5, babel-preset-es2015-node etc...If targets option is not used, @babel/preset-env behaves exactly the same as @babel/preset-es2015, @babel/preset-es2016 and @babel/preset-es2017 together (or the deprecated babel-preset-latest).

@babel/polyfill
Emulates a full ES2015+ environment (e.g. allows using Promise type)

@babel/plugin-transform-typescript
Plugin which transforms TypeScript into ES.next.

@babel/plugin-transform-arrow-functions
Plugin that transforms arrow functions into ES5 compatible function expressions.

@babel/plugin-transform-runtime
A plugin that enables the re-use of Babel's injected helper code to save on codesize.
But it's also required (together with @babel/runtime) when using Promise type or async-await idiom (otherwise an error like Uncaught ReferenceError: regeneratorRuntime is not defined might appear; regenrator is one of options of this plugin).

@babel/runtime
Contains Babel's modular runtime helpers and a version of regenerator-runtime.
Required when using Promise type and/or async-await syntax.

@babel/plugin-proposal-object-rest-spread
Allows using:
rest argument for functions; introduced in ECMA2015 (ES6)
spread syntax; introduced in ECMA2015 (ES6)

etc...

How to install Babel?


Create package.json:

> npm init

Install Babel locally (read here why):

> npm install --save-dev @babel/core @babel/cli

We also added them to dev dependencies as they are used during app development only (they are not needed in the runtime). [Do you put Babel and Webpack in devDependencies or Dependencies?]

If you try to install @babel/cli without @babel/core, npm will tell you that cli requires npm as a peer dependency. CLI acts as a plugin for Core library which makes sense.

>npm install --save-dev @babel/cli
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN @babel/cli@7.1.0 requires a peer of @babel/core@^7.0.0-0 but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.4 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.4: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ @babel/cli@7.1.0
added 153 packages from 108 contributors and audited 2145 packages in 10.359s
found 0 vulnerabilities

Furthermore, if we try to run babel CL executable, it will throw an error complaining it can't find the core:

C:\dev\github\babel-demo\node_modules\.bin>babel
internal/modules/cjs/loader.js:583
    throw err;
    ^

Error: Cannot find module '@babel/core'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:581:15)
    at Function.Module._load (internal/modules/cjs/loader.js:507:25)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at _core (C:\dev\github\babel-demo\node_modules\@babel\cli\lib\babel\options.js:29:16)
    at Object.<anonymous> (C:\dev\github\babel-demo\node_modules\@babel\cli\lib\babel\options.js:132:76)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)

So let's install the core:

C:\dev\github\babel-demo>npm install --save-dev @babel/core
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.4 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.4: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ @babel/core@7.1.0
added 33 packages from 16 contributors and audited 2344 packages in 7.878s
found 0 vulnerabilities

package.json now contains:

  "devDependencies": {
    "@babel/cli": "^7.1.0",
    "@babel/core": "^7.1.0"
  }

Babel CLI executable does not complain anymore:

C:\dev\github\babel-demo\node_modules\.bin>babel
babel:
  stdin compilation requires either -f/--filename [filename] or --no-babelrc


How to use Babel 



Let's now add a JS file which uses some of the recent JS features (e.g. arrow-style function):

C:\dev\github\babel-demo\src\scripts\index.js:

var log = x => console.log(x);
log('Hello, world!');

Now let's see what would Babel do with original JS code at this stage:

C:\dev\github\babel-demo\node_modules\.bin>babel ../../src/scripts --out-dir lib
Successfully compiled 1 file with Babel.

We can see that it didn't change anything apart from inserting an empty line between code lines:

C:\dev\github\babel-demo\node_modules\.bin>type lib\index.js
var log = x => console.log(x);

log('Hello, world!');

In the previous example we instructed Babel to compile the entire src directory and output it to the lib directory. Instead of that, as we're observing a single file, we could have specify a single input file and (single) output file:

C:\dev\github\babel-demo\node_modules\.bin>babel ../../src/scripts/index.js --out-file lib/index-transpiled.js

C:\dev\github\babel-demo\node_modules\.bin>type lib\index-transpiled.js
var log = x => console.log(x);

log('Hello, world!');


npx


Instead of going to node_modules\.bin in order to run babel executable (as it's installed locally) we can use a handy npm tool called npx:

C:\dev\github\babel-demo>npx babel
babel:
  stdin compilation requires either -f/--filename [filename] or --no-babelrc

Babel does not create directories in the output path:

C:\dev\github\babel-demo>npx babel src/scripts/index.js --out-file build/index-transpiled.js
{ Error: ENOENT: no such file or directory, open 'build/index-transpiled.js'
    at Object.openSync (fs.js:443:3)
    at Object.writeFileSync (fs.js:1163:35)
    at output (C:\dev\github\babel-demo\node_modules\@babel\cli\lib\babel\file.js:148:23)
    at C:\dev\github\babel-demo\node_modules\@babel\cli\lib\babel\file.js:234:9
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (C:\dev\github\babel-demo\node_modules\@babel\cli\lib\babel\file.js:74:103)
    at _next (C:\dev\github\babel-demo\node_modules\@babel\cli\lib\babel\file.js:76:194)
  errno: -4058,
  syscall: 'open',
  code: 'ENOENT',
  path: 'build/index-transpiled.js' }

...so we have to do it ourselves:

C:\dev\github\babel-demo>mkdir build

After this Babel runs successfully:

C:\dev\github\babel-demo>npx babel src/scripts/index.js --out-file build/index-transpiled.js

...and we can see the output file:

C:\dev\github\babel-demo>type build\index-transpiled.js
var log = x => console.log(x);

log('Hello, world!');

Output file being (almost) the same as the input file is not a surprise as we didn't specify any transformer (rules for transpilation). As in our JS file we used arrow functions, let's see how can we instruct Babel to transpile them into ES5 code. We have to install a plugin which contains rules for this transformation. We mentioned it earlier, it's @babel/plugin-transform-arrow-functions.

We'll install it locally:

C:\dev\github\babel-demo>npm install --save-dev @babel/plugin-transform-arrow-functions
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.4 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.4: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ @babel/plugin-transform-arrow-functions@7.0.0
added 2 packages from 1 contributor and audited 2346 packages in 6.998s
found 0 vulnerabilities

We can now instruct Babel to use this plugin via --plugins argument:

C:\dev\github\babel-demo>npx babel src/scripts/index.js --out-file build/index-transpiled.js --plugins=@babel/plugin-transform-arrow-functions

Output file now contains ES5-compatible code:

C:\dev\github\babel-demo>type build\index-transpiled.js
var log = function (x) {
   return console.log(x);
};

log('Hello, world!');

We can instruct Babel to automatically run each time we change input files by adding option --watch to its command line arguments.

To instruct Babel to transform all ES2015+ features in our code we would need to install a long list of plugins. Instead of this, we can install env preset which contains them all:

C:\dev\github\babel-demo>npm install --save-dev @babel/preset-env
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.4 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.4: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ @babel/preset-env@7.1.0
added 70 packages from 13 contributors and audited 3492 packages in 19.331s
found 0 vulnerabilities

To instruct Babel to use a preset we'll use --presets option in command line arguments:

C:\dev\github\babel-demo>npx babel src/scripts/index.js --out-file build/index-transpiled.js --presets=@babel/env

We can see that output now has an extra line ("use strict";):

C:\dev\github\babel-demo>type build\index-transpiled.js
"use strict";

var log = function log(x) {
  return console.log(x);
};

log('Hello, world!');

Source Code


Source code used in this article can be found on my GitHub account: babel-demo.

References &  Future Read:

Everything you need to know about BabelJS

No comments: