JavaScript Transpilation
by Nicklas EnvallA transpiler is a source-to-source compiler, converting source code to source code in the same or another language. A common reason for using transpilers is to enable the possibility to use the latest features before they're supported by browsers natively. Consequently, a transpiler compiles our code down to ES5 or whatever we target, enabling us to use new features without worrying about widespread support.
Transpilers also allow us to write in different languages than JavaScript while still targeting JavaScript. We can write in languages like CoffeeScript, Dart, Elm, ClojureScript, or TypeScript and then transpile that code into JavaScript. As a result, transpilers give us room to experiment. For example, before we decide to add something to the ECMAScript specification, we'll have data available to see if it's truly a wise decision.
So transpilers allow us to write in a certain way during development which we convert to an acceptable form before deploying it to production.
Transpiling, Polyfilling, and Shims
Transpiling and polyfilling are not the same. When transpiling our code, we're transforming parts of our syntax to the desired format. With polyfilling, on the other hand, we are implementing new functionality to the browser that it doesn't natively have. So transpilation can look like the following example where we transform a so-called arrow function to a "normal" function:
const add = (x, y) => x + y; // transforms to: "use strict"; var add = function add(x, y) { return x + y; };
While in our polyfill example, we add the includes
function:
if (!String.prototype.includes) { String.prototype.includes = function(search, start) { 'use strict'; if (search instanceof RegExp) { throw TypeError('first argument must not be a RegExp'); } if (start === undefined) { start = 0; } return this.indexOf(search, start) !== -1; }; }
Then we also have shims. A shim adds or replaces functionality by either adding new things to an old environment or old functionality to a new environment. Thus, by that definition, a polyfill is a shim that adds new functionality that the browser doesn't already support.
The Babel Transpiler
Babel is a very popular transpiler that can take care of both transpilation and polyfilling. Yet, Babel doesn't do anything by default. We need to configure Babel with plugins. Plugins are just small programs that let Babel know how to transform your code. But keeping track of multiple plugins is a hassle so, we can use presets, which are a set of carefully picked out plugins.
We create a configuration file to tell Babel what plugins and presets to use. The name of the configuration file varies, and common names are .babelrc.json
, .babelrc
, or .babel.config.json
. Though, each one may be more applicable than the other in some cases. For instance, the .babelrc.json
file gets used when we want it to apply to a single part of our project, while the babel.config.json
file gets used for monorepos:
{ "presets": ["@babel/preset-env"], "plugins": [], }
To use Babel, we start by installing the @babel/core
package that contains the core functionality. Once that's done, we can install packages like @babel/cli
that allow us to initiate the transpilation via our terminal.
Then we'll need to install a plugin or preset. In our example above, we use the @babel/preset-env
preset, which is a straightforward preset that allows us to use the latest JavaScript.
npm install --save-dev @babel/core @babel/cli @babel/preset-env
Now, imagine a file called es6.js
that contains arrow functions and whatnot. We can then, with our configuration, use @babel/cli
to transpile that code and output the result into another file.
./node_modules/.bin/babel es6.js --out-file es5.js
Babel looks in our .babelrc.json
file and transpiles the code from es6.js
down to ES5 and outputs the result in es5.js
. The @babel/cli
package is not the only package out there. We also have packages like babel-node
and babel-register
. Furthermore, there's a lot of plugins and presets out there that you can discover. There's also more to Babel. For example, you can reduce the number of plugins used even though they're in the preset by configuring Babel to target specific browsers.
The TypeScript Compiler
TypeScript is a superset of JavaScript and gives us a type system with optional type checking. Hence, it's appreciated by developers used to statically typed languages like Java and C#. TypeScript provides us with the TypeScript Compiler (abbreviated TSC), and therefore we don't need both Babel and TypeScript to use the latest features of JavaScript.
Let us take a closer look at how to use TypeScript's compiler and how we configure it. If you want to know more about how to use TypeScript, then feel free to read Introduction to TypeScript, a previous article of mine that introduces you to the language.
After installing typescript
locally, we can run tsc --init
to set up a TypeScript project. The command will create a tsconfig.json
file, and the directory that the tsconfig.json
is in will be the project's root. As you will see, the JSON object in the file will contain a compilerOptions
property that has some default options and commented-out options. You can study the commented-out ones to see if you want them or not. In this example, for simplicity sake, we remove the comments and some default options and end up with the following file:
{ "compilerOptions": { "target": "es5", "module": "commonjs", "outDir": "./dist", "strict": true, } }
As we see, we target ES5, which means we'll transpile our code to ES5. If you have a src/index.ts
and run tsc
it'll output the result in dist/index.js
. You can do much more with TypeScript, like excluding or including files. It even has a "watch mode" that's started with tsc -w
.
On a side note, only because you don't need Babel to use TypeScript doesn't necessarily mean you don't want. You can convert TypeScript to JavaScript with Babel and the @babel/preset-typescript
preset. It's common to use Babel for TypeScript if you have a pipeline build.