Harmony UI

Wednesday, July 17, 2024

How Compiler Technology is used to integrate AI Generation with React Codebases

Braydon Jones
A crossed out European emblem

Code Generation

Right now, the AI landscape is dominated in large part by code generation. Tools like Vercel’s v0, Magic Patterns, and Code Parrot allow you to generate React code from text or figma designs — decreasing the time from an idea’s conception to shipping to production.

These AI tools allow you to go from 0 to 80 percent relatively quickly--which does in fact save a lot of time. However, the problem is that if you ask anyone in product development, they will tell you the 80 to 100 percent is what takes the most amount of time. The reason is because of Iteration. AI does not eliminate the need for iteration, and that iteration is best done inside of the context of the codebase.

Ideally you could use these AI tools to iterate inside your own code base, but the problem is that they do not currently integrate. The developer has to transfer the generated code into the context of the codebase, and this makes iteration hard.

Luckily, we are not too far off from the possibility of using AI inside the context of the codebase. Github Copilot is trying to do this right now with their coding copilot. However, it still has a long way to go in order to produce consistently accurate results that work both at runtime and compile time. The key to making this work consistently is actually a decades old compiler technology: the abstract syntax tree.

The Abstract Syntax Tree

Most developers do not know what an abstract syntax tree (AST) is, but use tools based on it on an almost daily basis. Ever wonder how a linter knows how to lint? Or a formatter how to format? How does it actually change the code that stays syntactically correct?

The abstract syntax tree is a tree-like structure that represents the syntax of a program. It takes the code and turns it into AST nodes that represent different parts of the syntax. In javascript, const str = 'hello world'; translates to a VariableDeclarator node with an Identifier named str and a StringLiteral with a value of hello world.

Babel is the most popular transpiler used in React applications. It takes your React code and transpiles it into legacy supported javascript code. This transpiler provides a lot of apis that allow you to very easily manipulate the program. For example, if a linter wants to warn of unused variables, babel provides a way to see where a particular VariableDeclarator’s Identifier node is being used. If it’s not being used, you have access to the location of this node to make a warning. A formatter can then come in and just delete the node, updating the changes in the saved code file.

Check out the babel handbook, which talks more extensively about how an abstract syntax tree works and how to modify the nodes using the visitor pattern.

Abstract Syntax Tree with AI

This technology’s superpower is being able to edit existing code, which is why I hope you can see why this is a good fit for using AI to iterate on existing code. But how exactly can that be accomplished, especially in a highly dynamic Saas codebase?

With the babel transpiler, you can create a mapping of the relationship of the dynamic AST nodes. For example, let’s say you have this code:

const Component = ({className, children}) => <div className={className}> {children}</div>const Another = () => <Component className="flex">Hello world</Component>

From the AST, we know that the div in Component has a JSXAttribute that has an identifier className and value className. When we parse the Another component, we see that it is calling our Component component with the JSXAttribute className and value flex. If we keep track of this information, we can “map” the className in Component with the value flex in Another.

What this mapping tells us is that if we need to change the className property of the inner div inside Component, we have to do it at the StringLiteral node flex. This approach goes from prompting the AI “Take this big block of code and modify it so the component has border and padding” to “Given the existing tailwind css class “flex”, modify it so that it also has border and padding”. Then all we have to do is update the StringLiteral node from “flex” to what the AI outputs. This approach gives the AI better context and mitigates the possible erroneous output from changing a whole block of code.

This is how we are able to provide such high-quality code updates at harmonyui.app. We built the infrastructure using the abstract syntax tree, and that allows it to take in AI with the necessary context of the code base very easily, greatly increasing the effectiveness of making code updates to a React codebase.