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.

Hack with your soul – notes from the latest team hackathon (Oct 3, 2014)

As we dig deeper into building our product, it happens with time and fatigue that we pile up our list of to-dos. More often that not, they fail to get our attention. The engineering team decided to rejuvenate itself by delving into an energized session of a day long hackathon with intense code-love, music [yeah, Pink Floyd], and pizza on the house. The theme of this hackathon was hack with your soul!


To begin with, we proposed a set of modules on which smaller groups could devote their day hacking into, for a solution. While most were figuring out that one thing to work on, our code monkey, Nitin set out to work on two! One is a plugin for Chrome and Firefox to help sales team give live demo to prospects. The other, an even more exciting feature of search functionality based on lexical structure – it is like this universal gateway into all the functions and features of the WebEngage suite. Nitin also set out on his third hack towards the end of the day!

Vikas and Aayush teamed up to explore the feasibility of using actions in email. With the proposed hack, a call-to-action text can be provided in the subject line of an email using which a parser can do something pre-defined with the content in that email. This, incidentally, also served as an opportunity to do the much needed re-factoring of our email templating system [yeah, we use a lot of it in our system].

A quick hack by Ankit was to integrate WebEngage with Pipedrive. The most interesting twist to this one was that now, when you are using our on-site surveys for lead generation, the responses (leads) can be directly pushed into your sales CRM via our WebHooks – and, with this hack, now into Pipedrive!

Prashant, Rakesh, Manish, Radhika and Siya  teamed up for a very ambitious cause. Together they set out for developing an error capturing and visualization platform. A platform that would log JavaScript errors (in our integration code on third party sites) and create a statistical visualization for dev team to get more insights. It was almost impossible to do this in a day but a focused team effort helped the group pull this off with ease. We are super excited to roll out this hack in production.

And yes, we had support and participation from non-developers in the team too!

It has been a wonderful hack day and with loads of beer, Pink Floyd, pizza, hot tea, and code. Expect a some posts in the next couple of weeks with finer details on those hacks.

Love what we do? Come, hack with us. We are looking for you.

WebEngage Notification JavaScript Callbacks – Building A Realtime Conversion Engine On Your Website

Note: Breaking news first – we recently released our enhanced integration code which now supports a wide variety of JavaScript callbacks. If you are using WebEngage on your website, we recommend upgrading to the new code.

If you are new to WebEngage, take a quick live demo first – you’d love it!

Last week, we posted about our new integration code and how the callbacks can be put to some great use to build a “listening post” on your website for customers submitting feedback. Take a look at that here. This post focuses on how you can build some insanely beautiful interactions on your website using properties and callbacks in our Notification JavaScript API.

JS API awesomeness in Notification product

Our Notification product is a simple push messaging tool for your website. It allows you to display offers, run promotions and pop system alerts on your site from the comfort of WebEngage Dashboard – without changing any code on your site! Moreover, these messages can be “targeted” based on a bunch of rules. E.g. you can choose to display an offer ONLY to users “who are currently on the cart page AND haven’t checked out in 60 seconds AND have added atleast goods worth $200 or more”. Here’s more on targeting capabilities. You love us already? Wait, we haven’t even gotten started yet :-)

Notification is an out-of-the-box product wherein all UI/targeting level customizations can be applied from the WebEngage Dashboard. That said, using our brand new JavaScript API, you can do a whole lot more – making the notification content personalized, building real-time interactions with users on a particular offer/deal etc. Take a look at the samples below.

The low hanging fruits first …
By default, a notification doesn’t get displayed to the same user once (s)he has closed the notification or clicked on any link/call-to-action in the notification. Now, by setting a property forcedRender to true, you can override this behavior. Take a look at the code sample and documentation below for other such properties. Add the corresponding properties to your integration code and see the magic unfold!

// by default, a notification doesn't get displayed to the 
// user once (s)he has closed it or clicked on any link/call-to-action 
// button in the notification. set this property to true
// if you want to override this behavior
_weq["webengage.notification.forcedRender"] = true;
 
// the WebEngage integration code pops a notification on 
// your website based on preset rules in the dashboard. no
// invocation is needed; however, if you'd like to manually 
// invoke a notification, you should set defaultRender to false
_weq["webengage.notification.defaultRender"] = false;
 
// render a particular notification by specifiying its ID
// you can get the ID of a notification from your notifications
// list page in the dashboard. Also, a notification gets
// rendered based on rules in the targeting section; if you'd
// like to skip rule execution, you'll need to additionally
// set skipRules to true 
_weq["webengage.notification.skipRules"] = true;
_weq["webengage.notification.notificationId"] = "~10cb5811a";

Creating hyper personalized messages for your users
We support tokens, which essentially is a way to display dynamic content inside notifications. Take a look at the notification below, as seen in the notification editor.

You can use any number of [[tokens]] in the editable fields – title, description, call-to-action text and URL. Through our JavaScript API, you can replace these tokens with realized values are shown underneath.

// to replace the tokens with their corresponding values, you'll need to 
// create a JSON key/value pair for all the tokens present in your 
// notification (across fields)
_weq["webengage.notification.tokens"] = { 
  userName: "John Doe"
};
 
// let's make this truly dynamic by asking you for a name which can be 
// dynamically replaced inside the notification (shown in pic above). 
// Underneath is a code. Give it a try by clicking on the "try me" 
// button below.
$("a.try", $(".notification-tokens")).click(function(){
  var name = prompt("Please enter your name", "John Doe");
  if(name != null){
    name = (name == "") ? "John Doe" : name;
    _weq["webengage.notification.tokens"] = { 
      "userName": name
    };
 
    // we don't want WebEngage widget to automatically render an 
    // applicable notification as per the rules set in the dashboard
    _weq["webengage.notification.defaultRender"] = false;
    _weq["webengage.notification.skipRules"] = true;
 
    // we want to show the notification everytime you do a "try now" - 
    // irrespective of whether you have closed the notification once or 
    // have clicked on the call-to-action button; by default we don't 
    // show the same notification to a user once an action has been 
    // taken; this let's us override the behavior
    _weq["webengage.notification.forcedRender"] = true;
 
    // this is THE notification we want to render on this page
    _weq["webengage.notification.notificationId"] = "3176124c";
 
    // finally, render the notification!
    webengage.notification.render();
  }
  return false;
});

Implementing custom targeting rules & callback events
Custom targeting is an interesting way to display a notification (an offer / discount / recommendation etc) based on application specific criterion. E.g. Let’s say, you sell insurances online and you’d like to push a “child education plan” to customers while they are using your product online. However, you’d like to display this message ONLY to customers who have a “family group insurance”, have “atleast 1 kid” and have an “annual family income of more than $60,000″. Sounds cool? Here’s how you create such a notification and set-up its targeting criteria in your dashboard:


/* Implementing Custom Rules */
 
// for the custom rules, as shown above, to take effect on your website
// you'd need to pass the actual values of parameters hasGroupInsurance, 
// numberOfKids and annualFamilyIncome for logged in users.
_weq['webengage.notification.ruleData'] = {
  hasGroupInsurance: true,
  numberOfKids: 2,
  annualFamilyIncome: 75000
};
 
// with just three lines of the code above on your website, you can now
// target your notifications based any or all of the above mentioned
// customer attributes. Click the "try this example" button to see how
// this works with token data as underneath
_weq['webengage.notification.tokens'] = {
  userName: "John",
  kidsCurrentAge: 2,
  genderText: "her"
};
 
/* Implementing Callback On Click Events */
 
// first, you need to enable WebEngage callbacks for your widget
_weq['webengage.enableCallbacks'] = true;
 
/* onOpen callback */
 
// binding onOpen callback for your notifications
// this gets triggered anytime a notification pops-up on your website
_weq['webengage.notification.onOpen'] = function(data){
 
  // fetching the clickstream information for current user
  var userIP = (data.activity.ip ? data.activity.ip : "Unknown");
  var estimatedUserGeo = data.activity.city + " (" + data.activity.country + ")";
  var browser = data.activity.browser + " (" + data.activity.browserVersion + ")";
  var platform = data.activity.platform;
  var URLofThePageOnWhichFeedbackWasSubmitted = data.activity.pageUrl;
  var titleOfThePageOnWhichFeedbackWasSubmitted = data.activity.pageTitle;
  var referrer = data.activity.referrer;
 
  // the notification which got clicked
  var notificationId = data.notificationId;
  var notificationTitle = data.title;
 
  // if you are using tokens in your notification, this array
  // will contain all of them and have their corresponding values/defaults etc
  var tokens = [];
  for var(i=0; i<data.tokenData.length; i++){
    var aToken = data.tokenData[i];
 
    // the identifier for each token. e.g [[userName]]
    var tokenName = aToken.name;
 
    // you can set default value for each token in the dashboard
    var tokenDefaultValue = aToken.defaultValue;
 
    // the value set for each token in real-time via the JS API
    var tokenRealValue = aToken.value;
 
    tokens.push(aToken);
  } 
};
 
/* onClick callback */
 
// binding onClick callback for notifications
// if you attach this listener, every time a notification gets clicked,
// this function will be invoked
_weq['webengage.notification.onClick'] = function(data){
  // other than all the properties mentioned above, you'll
  // get these extra properties in the click event - the URL
  // and the text on the link/call-to-action button that got clicked
 
  var actionLink = data.actionLink;
  var actionText = data.actionText;    
};
 
/* onClose callback */
 
// triggers everytime a user clicks on the close button of the notification
// the data object, passed to this method, is the same as above
_weq['webengage.notification.onClose'] = function(data){
};

Custom data & advanced reporting
Using custom data, you can pass more information related to the customers viewing a notification or clicking on the call-to-action button. That ways, you can dice-and-slice the clickthrough information in your reporting section based on all the data you passed. Underneath is a code snippet of how to pass custom data and below that is a screenshot from the reporting section on how can clickthroughs be filtered based on these properties. Awesome. No? ;-)
/* Implementing Custom Data */
 
// you can add a JSON key-value map to this property
// when clicks get recorded, this data gets saved along with click
// in your reporting module (screenshot below), you can filter the
// clickthrough data based on these properties
 
// supported data types: string, date, boolean, integer
// you can add any number of fields depending upon your plan
_weq['webengage.customData'] = {
  underTrial: false,
  domain: "userwebsite.com",
  daysInUse: 10,
  email: "admin@userwebsite.com"
};

That’s not all! You can do much more with other options in the API. Check out the documentation for JavaScript API (version 4.0) – docs.webengage.com along with our REST API’s and Webhooks.

Hope you liked what you saw. Do share your feedback on the API. A similar post here on how to use the JS API for our feedback product.

Stay tuned. We love you!

WebEngage Feedback JavaScript Callbacks – Building A Realtime Customer Feedback Listening Post On Your Website

Note: Breaking news first – we recently released our enhanced integration code which now supports a wide variety of JavaScript callbacks. If you are using WebEngage on your website, we recommend upgrading to the new code.

If you are new to WebEngage, take a quick live demo first – you’d love it!

This is how our new integration code looks like:
<script id="_webengage_script_tag" type="text/javascript">
  var window._weq = _weq || {};
  _weq["webengage.licenseCode"] = "_LICENSE_CODE_";
 
  (function(d){
    var _we = d.createElement('script');
    _we.type = 'text/javascript';
    _we.async = true;
    _we.src = (d.location.protocol == "https:" ? 
               "//ssl.widgets.webengage.com" : "//cdn.widgets.webengage.com"
              ) + "/js/widget/webengage-min-v-4.0.js";
 
    var _sNode = d.getElementById('_webengage_script_tag');
    _sNode.parentNode.insertBefore(_we, _sNode);
  })(document);
</script>

In the previous version of our code, initialization of the widget was wrapped inside an onReady method. We kept it as such because of the async nature of our code plus a need to efficiently manage separate configuration and settings for all the three products from a central place once all the remote resources finished loading.

We got rid of that functional approach in this version. Instead, we created a JSON data structure with predefined keys that you can use to assign properties and attach callbacks for different events in context of each of the products.

JS API awesomeness in Feedback product

Our Feedback product allows you to create and configure a simple feedback tab for your website. You can add custom fields (radio buttons, text-boxes, dropdowns, file upload fields etc), create logical fields which are category dependent, build email routing rules and do a whole lot more from the WebEngage Dashboard. You can read more about the our feedback product here. The product is an out-of-the-box solution for your website. However, with the new integration code, you can add some interesting callbacks on your website to enhance the experience of users submitting feedback on your website. Underneath are a few samples.

UI level customizations
You can choose the default alignment, colors etc of the feedback tab for your website from the dashboard itself. However, let’s say on one of the sections on your site, you’d like to change these properties. Now, with the JS API, it is possible to do so. Just add these two lines to your integration code script on those pages/sections and see the magic unfold ..
_weq["webengage.feedback.alignment"] = "left";          // or, "right"
_weq["webengage.feedback.backgroundColor"] = "#cc9900"; // hex code

Form data handling and callbacks
With the new API, not only can you pre-populate the feedback form for your signed-in users, but you can also set field level attributes (like make mandatory, move to hidden field, reduce category options etc). So, let’s say, on a certain section of your site, you only wanted to display 2 categories in the dropdown instead of all, you can do that with formData property as shown below.

The new integration code (API) also provides hooks for onOpen, onClose, onSubmit events thereby giving you the ability to implement awesome user experiences for customers submitting feedback on your website. So, you can now build something like this – offer a discount coupon code to users submitting feedback on the order confirmation page! Sounds cool? Read through the code and comments below:

/* use this initialization property to pre-populate feedback form w/ user data */
_weq['webengage.feedback.formData'] = [ 
  // array of JSON's for each field in the form
  {
    // pre-populate the field with label "email"
    name: "Email",
 
    // set the value here
    value: "john@doe.com",
 
    // change the rendering mode of this field. other options are:
    // "readOnly" - to prevent the user from changing the value passed above
    // "hidden" - to pass as hidden variable without showing the
    // field to the user
    mode: "default"
  }, 
 
  {
    // pre-populate the field with label "Category"
    name: "Category",
 
    // overrides what was set in the dashboard
    isMandatory: true,
 
    // if you intend to show only a few categories on a certain page
    // use this property to specify the options you'd like to show
    // these categories should be pre-created in the dashboard first
    options: [
      "Billing Inquiries", 
      "Sales/pricing related query",
      "General feedback"
    ]
  }
];
 
/* Callback events - the fun begins now! */
_weq['webengage.enableCallbacks'] = true;
 
// So, let's say you wanted your webpage to be made "aware" everytime
// any user is either opening the feedback tab or submitting a feedback.
// Underneath is how you can attach your callback functions to these events
 
/* Feedback onOpen callback - triggers everytime a user clicks on the */
/* feedback tab on your website */
_weq['webengage.feedback.onOpen'] = function(data){
  $("feedback-open-data-display").html(function(){
    return 
      "User data: [" + 
        "IP - " + data.activity.ip + 
        "Browser - " + data.activity.browser + 
                     "(" + data.activity.browserVersion + ")" +
        "Platform - " + data.activity.platform +
      "]" +
      "Page data: [" +
        "URL - " + data.activity.pageUrl +
        "Title - " + data.activity.pageTitle +
      "]";
  });
};
 
/* Feedback onSubmit callback - triggers everytime a user submits */
/* feedback via the tab on your website */
_weq['webengage.feedback.onSubmit'] = function(data){
  // in addition to the data that you get inside the onOpen method
  // you also get data submitted for each of the form fields 
  // along with user's geography etc.
 
  // iterating over the fields and fetching user submitted data
  var userResponse = {};
  for(var i=0; i<data.fields.length; i++){
    var field = data.fields[i];
 
    // while building your feedback form, you can choose any
    // kind of field - textbox, commentbox, radio-buttons,
    // checkboxes, file upload etc.
    // depending upon the field type, we wrap the responses
    // submitted by a user with corresponding "class" key in
    // the field object. E.g. for the auto screenshot field, when
    // used by a user, we provide a URL of the corresponding image
    // thumbnail that gets generated. for a detailed response
    // object view, click the "view response data object" button below
 
    var fieldValue = "";
 
    // auto screenshot field
    if(field.value["@class"] == "snapshot"){
      fieldValue = field.value.thumbnailImageUrl;
    }
 
    // fields which produce a list of responses
    // e.g. multi-select, checkboxes etc
    else if(field.value["@class"] == "list"){
      fieldValue = field.value.values.join(", ");
    }
 
    // fields which produce a textual response
    // e.g. textboxes, comment boxes etc
    else if(field.value.text){
      fieldValue = field.value.text
    }
 
    userResponse[field.label] = fieldValue;
  }
 
  /* Additional Information */
 
  // along with data submitted in the feedback form, our
  // API also shares with you additional details to
  // provide more context around the submission.
 
  var feedbackSubmittedOn = new Date(data.activity.activityOn);
  var userIP = (data.activity.ip ? data.activity.ip : "Unknown");
  var estimatedUserGeo = data.activity.city + " (" + data.activity.country + ")";
  var browser = data.activity.browser + " (" + data.activity.browserVersion + ")";
  var platform = data.activity.platform;
  var URLofThePageOnWhichFeedbackWasSubmitted = data.activity.pageUrl;
  var titleOfThePageOnWhichFeedbackWasSubmitted = data.activity.pageTitle;
 
  /* Taking action on user submitted data */
 
  // at this point, you know what data did the user submit
  // for each of the fields - name, email, category etc.
  // so, e.g. if you wanted to redirect the user submitting a sales
  // inquiry to your lead generation page and pass his/her
  // email to that page, here's how you can do it:
 
  if(userResponse["Category"] == "Sales/pricing related query"){
    // automatically close the feedback tab first!
    webengage.feedback.clear();
 
    // now, redirect the user to the lead page
    window.location.href = 
      "http://mywebsite.com/lead.html?email=" + userResponse["Email"];
  }
};

Wait, that’s not all! You can do much more with other options in the API. Check out the documentation for JavaScript API (version 4.0) – docs.webengage.com along with our REST API’s and Webhooks.

Hope you liked what you saw. Do share your feedback on the API. Here’s a similar post on how you can use our JavaScript API with the Notification product to build a realtime conversion engine for your website.

Stay tuned. We love you!