Modernizing Your Codebase with Lebab
Tags: javascript, node, webdev
Published: 2023-05-29 11:55:37

*Originally published in 2017*

Why walk if you can take a bus? Why rewrite old ES5 code manually if you can use some program to do that for you? But first, let me introduce ECMAScript 6 to you – the best thing that has ever happened to JavaScript. However, as same as all innovations, that standard also has many problems.

One of them is browser support. It’s steadily improving, but it’s not like everyone is keeping software up to date on their computers. Transpilers, like [Babel](https://babeljs.io/) and [Traceur](https://github.com/google/traceur-compiler), come to the rescue. Thanks to them we can use most of the new functions. [On that website](https://kangax.github.io/compat-table/es6/), we can check which ones are supported.

The second problem is the opposite of the first one. Existing scripts contain millions of lines of code written in ES5. If, as the authors, you want to start using the newest version of that standard, rewriting everything is pretty time-consuming. Is it possible to correct that? Can we, in a fast and pleasure way, from that code

```js
var $ = require('jquery');

$('.menu-element').each(function() {
    var list = $(this).attr('data-list').split(',');

    for (var i = 0; i < list.length; i++) {
        var item = list[i];
        $(this).append('<div>' + item + '</div>');
    }
});
```

get such a code?

```js
import $ from 'jquery';

$('.menu-element').each(function() {
    const list = $(this).attr('data-list').split(',');

    list.forEach(item => {
        $(this).append(`<div>${item}</div>`);
    });
});
```

The answer is yes. And I will show you how to do that.

What we need is [Lebab](https://github.com/lebab/lebab). It works inversely as Babel, and it’s transpiling code fragments written in ES5 to their counterparts in ES6. Lebab is still in a development phase, but just now its capabilities are great. You can find the actual list of its functions on GitHub.

Lebab is available as a package in npm repository, so its installation requires one, simple command:

```
npm install -g lebab
```

Then we can start to work. Example Lebab call looks like that:

```
lebab input-es5.js -o output-es6.js --transform <transform-name>
```

We put required conversion type instead of `<transform-name>`. There are many types, e.g. `commonjs`, which transforms CommonJS modules into ES6 version, including `require` and `module.exports` calls.

Unfortunately, that approach has one disadvantage – we can’t use many conversions at the same time. To transform whole code, we need to call Lebab few or even dozen times.

We can also add Lebab to a script and run it in Node.JS. It allows giving transformation table to executing, and it requires just two lines of code.

```js
import lebab from 'lebab';
const { code, warnings } = lebab.transform('var f = function(){};', ['let', 'arrow']);
```

`Code` variable contains generated code, and `warnings` variable contains a table with possible errors.

Is Lebab actually good at transpiling code? I will try to answer that question by presenting few examples.

Let’s start with a simple code, which sums up even elements of Fibonacci code, smaller than 300. Here is a code written in old JavaScript:

```js
var fibonacci = [];
for (var oi = 0, i = 1; i < 300; i) {
    fibonacci.push(i);
    var temp = oi;
    oi = i;
    i = i + temp;
}

console.log(fibonacci);

var sum = 0;
fibonacci.forEach(function(item) {
    var isEven = !(item % 2);
    if (isEven) {
        sum = sum + item;
    }
});

console.log(sum);
```

Now we run Lebab:

```
lebab e1.es5.js -o e1.arrow.js --transform arrow
```

And let’s check the result code:

```js
var fibonacci = [];
for (var oi = 0, i = 1; i < 300; i) {
    fibonacci.push(i);
    var temp = oi;
    oi = i;
    i = i + temp;
}

console.log(fibonacci);

var sum = 0;
fibonacci.forEach(item => {
    var isEven = !(item % 2);
    if (isEven) {
        sum = sum + item;
    }
});

console.log(sum);
```

As we can see, a function was transpiled into arrow function. Unfortunately, it’s the only thing that has been changed. Let’s add a variable transformation to that. It’s marked as Unsafe, which means that it may not work properly, but in example file, it should work.

```
lebab e1.arrow.js -o e1.es6.js --transform let
```

The result code looks like that:

```js
const fibonacci = [];
for (let oi = 0, i = 1; i < 300; i) {
    fibonacci.push(i);
    const temp = oi;
    oi = i;
    i = i + temp;
}

console.log(fibonacci);

let sum = 0;
fibonacci.forEach(item => {
    const isEven = !(item % 2);
    if (isEven) {
        sum = sum + item;
    }
});

console.log(sum);
```

It looks like that the code has been transpiled in a pretty good way. But a limitation of one transformation per call makes the whole work laborious.

Let’s try the second approach, which I have mentioned before. A source file will be more complicated this time. A code consists of two classes, and one of them inherits after the second one through prototyping:

```js
function Car(brand, model, color) {
    color = color || 'black';

    this.brand = brand;
    this.model = model;
    this.color = color;
}

Car.prototype.drive = function() {
    console.log('Driving...');
};
Car.prototype.getInfo = function() {
    console.log('This is', this.color, this.brand, this.model);
};

function Truck(brand, model, color, capacity) {
    Car.call(this, brand, model, color);

    this.capacity = capacity;
    this.currentLoad = 0;
}

Truck.prototype = Object.create(Car.prototype);
Truck.prototype.constructor = Truck;

Truck.prototype.load = function(volume) {
    console.log('Loading', volume);
    var newLoad = this.currentLoad + volume;
    this.currentLoad = Math.min(this.capacity, newLoad);
};
Truck.prototype.unload = function(volume) {
    console.log('Unloading', volume);
    var newLoad = this.currentLoad - volume;
    this.currentLoad = Math.max(0, newLoad);
};
Truck.prototype.getInfo = function() {
    Car.prototype.getInfo.call(this);

    console.log('Capacity:', this.capacity);
};

var ford = new Car('Ford', 'Focus', 'silver');
ford.getInfo();
ford.drive();

var kamaz = new Truck('Kamaz', '55111', 'white', 13000);
kamaz.getInfo();
kamaz.load(500);
kamaz.drive();
```

Now we’re preparing a script, which will be responsible for reading our source code, converting it and saving it to a new file.

```js
let lebab = require('lebab');
let fs = require('fs');

let filename = 'e2.es5.js';

fs.readFile(filename, function(err, data) {
    let source = data.toString();
    const { code, warnings } = lebabThis(source);

    if (warnings.length) {
        console.log(warnings);
    }
    fs.writeFile('e2.es6.js', code, () => {
        console.log('Written');
    });
});

function lebabThis(source) {
    return lebab.transform(source, [
        'let',
        'class',
        'default-param',
    ]);
}
```

After running the script in Node.js, we receive an object code:

```js
class Car {
    constructor(brand, model, color = 'black') {
        this.brand = brand;
        this.model = model;
        this.color = color;
    }

    drive() {
        console.log('Driving...');
    }

    getInfo() {
        console.log('This is', this.color, this.brand, this.model);
    }
}

class Truck extends Car {
    constructor(brand, model, color, capacity) {
        super(brand, model, color);

        this.capacity = capacity;
        this.currentLoad = 0;
    }

    load(volume) {
        console.log('Loading', volume);
        const newLoad = this.currentLoad + volume;
        this.currentLoad = Math.min(this.capacity, newLoad);
    }

    unload(volume) {
        console.log('Unloading', volume);
        const newLoad = this.currentLoad - volume;
        this.currentLoad = Math.max(0, newLoad);
    }

    getInfo() {
        super.getInfo();

        console.log('Capacity:', this.capacity);
    }
}

const ford = new Car('Ford', 'Focus', 'silver');
ford.getInfo();
ford.drive();

const kamaz = new Truck('Kamaz', '55111', 'white', 13000);
kamaz.getInfo();
kamaz.load(500);
kamaz.drive();
```

Lebab also dealt with that case. If you want more examples of different transformations, take a look at [GitHub](https://github.com/kamil7x/lebab-examples).

Although Lebab is still in development phase, we can use it to boost migration to the newer code. Of course, it’s not an ideal tool. The creators of ES6 recommend using only one transformation at a given moment and checking if everything went as planned.

In the future, aside from adjustments of a conversion for the sixth edition of ECMAScript, it will also be a support for ES7. Will it be enough for Lebab to become popular? Time will show.

---

*Photo by [Blake Connally](https://unsplash.com/@blakeconnally) on [Unsplash](https://unsplash.com/)*