Skip to main content

A while back, I built an Aura to LWC Converter for fun, and here’s how I did it.

The Magic Ingredients

Here I’ll list everything from infrastructure components to software libraries which I used to implement the backend engine with its associated “business logic”.

Heroku – Hosting provider for the entire web application stack
Node.js – The server runtime of choice
jscodeshift – JavaScript library developed by Facebook to perform codemods
nearley – JavaScript library to convert a grammar definition of the Aura Expression language to accessible JavaScript modules
xml-js – JavaScript library to convert XML files in Aura to JSON structures which can be manipulated in JavaScript

Honourable shoutouts as well to JSForce and AST explorer, which I used to access each application user’s Salesforce data via OAuth and understand the nature of JavaScript Abstract Syntax Trees (ASTs) respectively.

The Background Knowledge

I am not an expert in this field. In fact, I did not even study computer science in university. So in a way I’m the perfect guy to provide here the layman’s overview of what you need to know about programming language design in order for what I’ve built to make sense.

The first, and most important concept to understand is that of Abstract Syntax Trees. Let’s take for example this Aura Component Controller:

 navToRecord : function(component) {
 $A.get("e.force:navigateToSObject").setParams({
 recordId: component.get("v.recordId")
 }).fire();
 }
 })

The idea is that a tree representation of the code can be generated as below (thanks to Online JSON to Tree Diagram Converter):

aodigy-image

By traversing and otherwise manipulating this tree, we can construct a different tree and then “reverse” back into the source code output of a different programming paradigm, as below:

import { LightningElement, api } from "lwc";
import { NavigationMixin } from "lightning/navigation";

export default class ExampleLWC extends NavigationMixin(LightningElement) {
  
    navToRecord() {
        this[NavigationMixin.Navigate]({
            type: "standard__recordRelationshipPage",
            attributes: {
                recordId: this.recordId,
                actionName: "view"
            }
        });
    }
}

This process of running code which alters other code is also known as codemod.

What about the Grammar?

What I’ve laid out above is the general process used to transform Aura Component Controllers and Helpers into LWC JavaScript files. With the help of the jscodeshift library, everything else was pretty much abstracted away from me. I just had to painstakingly go into AST explorer to understand every possible form of transformation from the Aura to the LWC JavaScript framework. (From ArrowFunctionExpression to YieldExpression, there’s only an estimated 50-odd number of AST node types 😊.)

In the case of Aura Expressions however, I wasn’t so lucky. Just to refresh our memories, an Aura Expression refers to something like this which you might find in the .cmp file:

<lightning:button label="Next"
 disabled="{!or((v.endPage + 1) >= v.totalRecordsCount, v.isConfirmClicked)}"
 />

There was no parser available for me to generate the AST of such expressions. And needless to say, regex is not the answer. So I had a choice: build my own parser or write a grammar definition based on the official copy, have nearley.js compile that into a parser module, use the Interpreter design pattern to map each composite item into its part of the equivalent class property in the LWC JavaScript file, and use the Visitor design pattern to augment additional behaviours such as comment generation.

Obviously I chose the latter, and for the insomniacs among us here’s a snippet of my grammar definition file:

aodigy-image 1

Here’s a really fun part where I try to define what is an illegal expression:

aodigy-image 2

To complete the example above, here’s how the converted LWC code will look:

// HTML file

<template>
    <lightning-button label="Next" 
        disabled={ersatz_v_endPage_plus_number_1_greaterThanOrEquals_v_totalRecordsCount_or_v_isConfirmClicked}></lightning-button>
</template>

// JavaScript file

import { LightningElement } from "lwc";

export default class ExampleLWC extends LightningElement {
   
    // Appending the "ersatz" string is one of my tactics for eliminating name collisions 
    get ersatz_v_endPage_plus_number_1_greaterThanOrEquals_v_totalRecordsCount_or_v_isConfirmClicked() {
        // For simplification purposes let's ignore the absent class properties
        return this.endPage + 1 >= this.totalRecordsCount || this.isConfirmClicked;
    }
}

The Closing Curtain

There were many twists and turns in the process, as besides the obvious complexities I also had to deal with unexpected design considerations along the way. What if I received syntactically incorrect input? Or worse, what if it was syntactically correct but semantically incorrect input which would throw a runtime error even in its current Aura form? What if 3 parameters were passed to an Aura framework method where only 2 were expected? Was I going to build a “dumb converter” or an “opinionated operator”? What if an Aura controller method shared the same name as a helper method in the same component bundle, and them living in the same LWC JavaScript file will lead to name collision? How should I handle unsupported LWC features?

But all in all this was the most fun thing I’ve ever built related to the Salesforce platform. There’s many known and probably unknown limitations since I didn’t have the time and energy to implement everything I wanted. I had documented all these on the site as well. Have fun exploring!

Some of our clients

clientclientclientclientclientclientclientclientclientclientclientclientclientclientclientclientclientclientclientclientclient