Modernizing Your Codebase with Lebab

kamil7x

Kamil Trusiak

Posted on May 29, 2023

Modernizing Your Codebase with Lebab

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 and Traceur, come to the rescue. Thanks to them we can use most of the new functions. On that website, 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

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>');
    }
});
Enter fullscreen mode Exit fullscreen mode

get such a code?

import $ from 'jquery';

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

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

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

What we need is 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
Enter fullscreen mode Exit fullscreen mode

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

lebab input-es5.js -o output-es6.js --transform <transform-name>
Enter fullscreen mode Exit fullscreen mode

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.

import lebab from 'lebab';
const { code, warnings } = lebab.transform('var f = function(){};', ['let', 'arrow']);
Enter fullscreen mode Exit fullscreen mode

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:

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);
Enter fullscreen mode Exit fullscreen mode

Now we run Lebab:

lebab e1.es5.js -o e1.arrow.js --transform arrow
Enter fullscreen mode Exit fullscreen mode

And let’s check the result code:

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The result code looks like that:

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);
Enter fullscreen mode Exit fullscreen mode

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:

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();
Enter fullscreen mode Exit fullscreen mode

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

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',
    ]);
}
Enter fullscreen mode Exit fullscreen mode

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

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();
Enter fullscreen mode Exit fullscreen mode

Lebab also dealt with that case. If you want more examples of different transformations, take a look at GitHub.

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 on Unsplash

💖 💪 🙅 🚩
kamil7x
Kamil Trusiak

Posted on May 29, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related