DEV Community

Clay Risser
Clay Risser

Posted on

Render Abstract Syntax Trees with React

github.com/codejamninja/react-ast

A software developer can only code so much in a given day, but one's productivity can increase exponentially through metaprogramming. According to the interwebs, metaprogramming is "a programming technique in which a computer program treats other programs as its data". Metaprogramming is great for solving repeatable problems.

For example, I publish a lot of open source code. This means I have to write a readme file, contributing guide, license, and changelog over and over again. Using metaprogramming I could build a yeoman generator that generates these files for me based on a few inputs, like my name, the current year and the project's name. This is exactly what I did a few years back with my generator-github-project.

A yeoman generator is basically a sugary templating engine. Templating engines are a great way to solve metaprogramming, but they have a serious flaw. They are very opinioned and almost impossible to compose. In other words, they solve a very specific problem and nothing else. If two generators overlap, it is almost impossible to reuse code between them.

Another approach to metapgoramming that does not have this limitation is manipulating the abstract syntax tree, usually called AST for short. An AST is basically a tree representation of source code. Abstract syntax trees are a powerful tool, but they are difficult to work with by nature.

ASTs and templating engines often face another problem in the metaprogramming world. The code used in these projects has a tendency to spaghetti quickly. This make is really hard to read and almost impossible for another developer to read.

npm
GitHub stars

Using React, I was able to build a tool that makes interacting with ASTs easy. React is composable by nature. This means I can break down the code generator into small modular components that can be used across many projects. Traditional techniques for interacting with ASTs usually involve imperative programming, which is hard to visualize and read. Since React is declarative by nature, understanding the logic behind the metaprogramming is very natural and scales across teams beautifully.

I got the idea for React AST after building a large code generator that interacted with Babel's AST. I wanted to create TypeScript bindings for GJS, Gnome's JavaScript runtime engine. Specifically, I wanted to create bindings for the GTK integration with GJS. However, the task was daunting. It would require about 30,000 lines of TypeScript bindings. The obvious solution was metaprogramming. I built a tool called ts-gir that generates TypeScript bindings from Gir files. The tool was successful. You can see the binding HERE.

However, though I saved myself months of time, the process was painful. Despite my best efforts, the code in ts-gir spaghettied, and it would not be easy for anyone to contribute to the project. Ironically, the reason I was building the TypeScript bindings in the first place was so I could build React bindings to GTK. I built a proof of concept using metaprogramming for react-gtk, but the code generator is almost unmaintainable. To properly move forward on the project I will have to rewrite the bindings.

With all these thoughts of React, metaprogramming, ASTs and code generators all floating through my mind, I inevitably thought of building a React renderer for ASTs. React AST certainly brings a new way of thinking about meteprogramming to the table. It will enable me to progress on my endeavor of building a React renderer that renders to GTK.

Ok, enough history. How does this actually work?

I have included a very simple tutorial below to give you an idea of what metaprogramming with React AST would feel like.

First the npm module needs to be installed. Simply run the following command.

npm install --save react-ast
Enter fullscreen mode Exit fullscreen mode

Create a file called renderCode.js and put the content below in the file.

import React from 'react';
import {
  ClassDeclaration,
  Code,
  FunctionDeclaration,
  Param,
  ReturnStatement,
  render
} from 'react-ast';

const code = render(
  <>
    <ClassDeclaration name="Hello" superClassName="Array">
      <Code>hello = 'world'</Code>
    </ClassDeclaration>
    <FunctionDeclaration
      name="add"
      params={[<Param key="a">a</Param>, <Param key="b">b</Param>]}
      returnStatement={<ReturnStatement>result</ReturnStatement>}
    >
      <Code>const result=a+b</Code>
    </FunctionDeclaration>
  </>
);

console.log(code);
Enter fullscreen mode Exit fullscreen mode

Make sure you have babel setup with the project.

.babelrc

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": {
          "node": "6"
        }
      }
    ],
    "@babel/react"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Generate the code by running the following command.

babel-node ./renderCode.js
Enter fullscreen mode Exit fullscreen mode

You should see the rendered code print out to the console.

class Hello extends Array {
  hello = 'world';
}

function add(a, b) {
  const result = a + b;
  return result;
}
Enter fullscreen mode Exit fullscreen mode

If you want to render the AST instead of the code, simply use the renderAst function.

import React from 'react';
import {
  renderAst,
  ClassDeclaration
} from 'react-ast';

const ast = renderAst(
  <ClassDeclaration name="Hello" />
);

console.log(ast);
Enter fullscreen mode Exit fullscreen mode

The AST will look something similar to this.

{ type: 'File',
  program:
   { type: 'Program',
     body: [ [Object] ],
     directives: [],
     sourceType: 'script',
     interpreter: null },
  comments: [],
  tokens: [] }
Enter fullscreen mode Exit fullscreen mode

There are several really powerful things about generating code this way. It is very easy to read and understand what is going on. This is increases productivity and reduces the chances of bugs.

Often, the author of a metaprogramming project chooses the coding standard of the generated output. However, since React AST actually will confirm to the coding standers of the user of the generator.

It also is extremely composable. For example, reusable snippets of code could easily be abstracted into a React AST component which can then be composed with other React AST components.

Right now there are just basic React AST components, like ClassDeclaration, FunctionDeclaration, and Code. However, these basic components can be composed to create more advanced components like ReactComponent or ExpressRoute.

I would love to see more of the community contribute to this project. Building a library of reusable and composable React AST components has a lot of potential.

Review the guidelines for contributing

If you have any questions or input, you can submit feedback and file issues at the link below.

Submit an issue

github.com/codejamninja/react-ast

Top comments (0)