Transforming our JavaScript code at build-time with Babel

WebEngage's transformation plugin for Babel

A few months ago, we started using ES6 template strings to embed fragments of HTML and CSS in our Web SDK. Template strings allowed us to inline multi-line content and expression interpolation served all our templating needs. We used Babel of course, to transpile these template strings to regular strings in the build, since we want our code to run even on browsers that don’t support template strings.

Now the transpilation preserves all whitespace characters it encounters in the template string as-is which means all the newlines, tabs and extra spaces that were used to indent and format the HTML and CSS fragments for code readability, would show up in the output and unnecessarily bloat the shipped code. Since these whitespace characters were components of strings, even minification or any text processing couldn’t remove them (how were we to distinguish strings containing the fragments from the others). We needed a point in our build process where we could smartly detect template strings containing the fragments and strip them of unnecessary whitespace. Luckily we discovered babel-plugin-dedent and used that as a reference to write our own whitespace removal Babel plugin.

A Babel plugin is most often written for source code transformations e.g. converting the ES6 arrow functions to ES5 compatible syntax. However there are other uses as well, such as static analysis of source code. Plugins only need to work on the transformations, Babel takes care of the hard parts – parsing your source code, constructing an abstract syntax tree (AST), traversing that AST, tracking all the transformations that plugins make on the AST and finally generating a source code out of the transformed AST for output.

A Babel plugin is simply a function returning an object of visitor methods. The names of these visitor methods indicate the type of AST nodes they want to process. While traversing the AST wherever Babel encounters a node of such a type, it calls this method passing it a path object.

Example of a basic Babel plugin (written as a CommonJS module)

module.exports = function (babel) {
  var t = babel.types;
 
  return {
    visitor: {
      BinaryExpression: function (path) {
        // *path* contains the visited node along with related nodes,
        // metadata and some utility methods
        console.log("Operator of binary expression is", path.node.operator);
      }
    }
  };
};

Coming back to our whitespace removal plugin, we had to tag our template strings containing HTML and CSS fragments in the source code. This was needed to easily identify template strings with insignificant whitespace (where extra whitespaces can be removed) later on in the build process. The name of the tag was chosen to be nowhitespace.

We wrote our plugin to visit TaggedTemplateExpression type nodes, use babel.types to check if the tag name was nowhitespace and perform the transformation i.e. remove extra whitespaces from the various parts of the template string (called quasis). We also had to remove the tag from the template string, so we simply replace the TaggedTemplateExpression node with the inner TemplateLiteral node. Plugins in babel-preset-es2015 then took care of converting TemplateLiteral nodes to a regular string.

Below’s a version of the plugin (whitespace removal code is simplified). You can see it in action at astexplorer.net.

var pattern = new RegExp("[\n\t ]+", "g");
 
function transfrom (quasis) {
  ["raw", "cooked"].forEach(function (type) {
    quasis.forEach(function (element) {
      element.value[type] = element.value[type].replace(pattern, " ");
    });
  });  
}
 
module.exports = function (babel) {
  var t = babel.types;
 
  return {
    visitor: {
      TaggedTemplateExpression: function (path) {
        let node = path.node;
 
        if (t.isIdentifier(node.tag, { name: "nowhitespace" })) {
          transfrom(node.quasi.quasis);
          return path.replaceWith(node.quasi);
        }
      }
    }
  };
};

We configured Babel to use our plugin in the build process. One can put the plugin module path in .babelrc. When Babel is used through its API, the plugin’s function can directly be passed in the plugins option

Since the first working iteration, we have generalized this plugin to do proper minification of the fragments using html-minifier and cssmin. Check out the plugin code on GitHub (note that it is still experimental).

Babel’s plugin ecosystem is growing day after day on npm. You’ll find many of them serve niche, custom use cases.

To aspiring plugin authors, we recommend reading up on the Babel Plugin Handbook by James Kyle. Also, you will find astexplorer.net quite handy in inspecting ASTs and trying out transformations.

Thanks for stopping by and happy transpiling.


I was motivated to write this post after my talk on “Writing your own Babel plugin” in the recently concluded JavaScript Meetup at WebEngage. Here’s the deck I used.

Automating git flow hotfixes with git lava!

At WebEngage, we use git flow as the branching model for our code repositories. It is a very helpful git workflow for collaborative development (read more).

How we use git flow at WebEngage

Whenever we need something fixed quickly on production we start on a git flow hotfix. This involves a bunch of steps:

  • update master and develop branches from origin (we deploy from the master branch on production)
  • look for the last hotfix version in git log to determine the next version
  • create a hotfix branch from master
  • fix the issue and commit changes in the hotfix branch
  • complete the hotfix by merging commits in master and develop
  • create a tag pointing at the merge commit in master
  • push the commits to remote master and develop branches
  • push the tag to remote

To aid in creating hotfixes, some of us use the command line git extensions (cheatsheet) while others prefer Atlassian SourceTree with its built in git flow support.

Even then the process is rather time consuming (especially when we are fire-fighting) and error prone with people creating hotfixes with same version every now and then.

Say hello to git lava

An anecdote from NARKOZ/hacker-scripts
“If something – anything – requires more than 90 seconds of his time, he writes a script to automate that”.

Hence, we wrote git lava – a small automation script over git flow for creating hotfixes.

With git lava we simply checkout the master branch, make code changes for the fix, test it out locally and then execute ..

git lava "commit_message" 

git lava scans git log and finds the next hotfix version itself, starts hotfix, commits the changes, completes hotfix and pushes the commits and tag to remote.

It even has a rollback mechanism in case of errors.

Here’s a demo of how it works …
git lava demo ...

git lava is on GitHub. Please feel free to use and contribute.

Before you go, lemme tell you why we named it git lava?
Well, because it automates HOTfixes with git FLOW → HOT FLOW → lava :clap:

Thanks for stopping by.