Writing a Babel Plugin with Grandma
Tags: javascript, babel, plugins, tutorial
Published: 2020-04-25 15:49:22

---
title: Writing a Babel Plugin with Grandma
description: "How to Write a Custom Babel Plugin 👵📚"
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/tp1impub9zxt6j696u58.png
canonical_url: https://stephencook.dev/blog/writing-a-babel-plugin-with-grandma
tags: javascript, babel, plugins, tutorial
published: true
---

I wrote a custom Babel plugin that lets you write React with emoji. It was not a productive use of time.

{% codesandbox like-grandma-made-jgri5 %}

## How To Write a Babel Plugin

In this post I’m going to walk you through how to make a Babel plugin, so you can make your own custom one, like [grandmas-own-jsx](https://github.com/stephencookdev/grandmas-own-jsx).

I _suppose_ you could also make a plugin that’s actually useful. I mean — I can’t physically stop you.

### What is Babel?

Before we add anything to Babel, we first need to understand it a bit. I assume you’re _roughly_ familiar with Babel (if not, [give this a read first](https://babeljs.io/docs/en/6.26.3/)) — but let’s break it down a bit.

At its heart, Babel does these 3 things: **parse**, **transform**, and **generate**.

![a hand-drawn diagram showing some code being parsed, transformed, then generated](https://dev-to-uploads.s3.amazonaws.com/i/tp1impub9zxt6j696u58.png)

In other words, it **parses** our _input code_ into an Abstract Syntax Tree (AST), **transforms** that AST into a different shape, and then **generates** code from the transformed AST.

“AST” sounds confusing, but it’s just a fancy way of saying “code, but represented as a tree”.

### What is a Babel Plugin

A Babel plugin is code that we can add into the **transform** step. It’s important to note that this is the _only_ step that we can influence. We can’t do anything to the **parse** step, which means _we can’t add custom syntax_.

In other words, we’re limited to transforming _from_ valid JavaScript, _to_ valid JavaScript. If we want to transform something that isn’t valid JavaScript, we would [need to modify Babel itself](https://github.com/babel/babylon/pull/5#issuecomment-195801336).

### How Do We Transform Code

To transform the code, we need to traverse the AST we get given from the **parse** step. We can traverse this using a **visitor**. Let’s look at this simple Babel plugin:

```javascript
module.exports = function () {
  const SimpleVisitor = {
    StringLiteral(path, state) {
      if (path.node.value === "We'll never survive!") {
        path.node.value = "Nonsense. You're only saying that because no one ever has.";
      }
    },
  };
  
  return { visitor: SimpleVisitor };
};
```

Now, we could write code like this:

```javascript
function fireSwamp () {  
  return "We'll never survive!";  
}
```

And it would run through Babel with our plugin, and compile to this:

```javascript
function fireSwamp () {  
  return "Nonsense. You're only saying that because no one ever has.";  
}
```

What’s happening here? The visitor is an object mapping from the name of an AST node, to a function describing what to do with that node. So when Babel sees the matching `StringLiteral`, our plugin kicks in and transforms the string’s value.

### How Do We Transform More Code?

So we’ve looked at a really simple Babel plugin — let’s make something a bit more complicated. We’ll break down the code in the [grandmas-own-jsx plugin](https://github.com/stephencookdev/grandmas-own-jsx), step by step.

```javascript
module.exports = function ({ types: t }) {
  const GrandmaVisitorInitiator = {
    Program(path) {
      const commentLineTokens = path.parent.comments.filter(
        (token) => token.type === "CommentLine"
      );
      const commentBlockTokens = path.parent.comments.filter(
        (token) => token.type === "CommentBlock"
      );

      if (!commentLineTokens.length || !commentBlockTokens.length) return;

      const grandmasReference = buildGrandmasReference(commentLineTokens);
      const grandmasRecipes = buildGrandmasRecipe(commentBlockTokens);

      path.traverse(GrandmaVisitor, {
        grandmasReference: grandmasReference,
        grandmasRecipes: grandmasRecipes,
      });
    },
  };
  
  return { visitor: GrandmaVisitorInitiator };
};
```

Since `grandmas-own-jsx` leverages comments, the first thing we do is look through the `CommentLine` and `CommentBlock` elements in the `Program` node. We use these comments to get the information we need to build our React elements later on.

Next, we set up a _new_ visitor to run on the rest of the tree. This lets us pass down state as we traverse the tree, without depending on global state.

```javascript
path.traverse( // traverse the tree down from the current node  
  GrandmaVisitor, // the new visitor  
  { ... } // the state our new visitor should get  
);
```

So let’s take a look at the new visitor:

```javascript
module.exports = function ({ types: t }) {
  const GrandmaVisitor = {
    StringLiteral(path, state) {
      if (path.node.value === "👵") {
        const recipeRef = state.grandmasRecipes[path.node.loc.start.line];
        const recipeMatches = recipeRef && recipeRef.start > path.node.start;
        if (recipeMatches) {
          const recipe = recipeRef.value;
          const domStruc = cookRecipe(recipe, state.grandmasReference);

          const typeExpression = genTypeExpression(domStruc);

          path.replaceWith(typeExpression);
        }
      }
    },
  };
  
  const GrandmaVisitorInitiator = { /* ... */ };

  return { visitor: GrandmaVisitorInitiator };
};
```

Here, we first look for any string that is just `"👵"` in our program. From the state that we were passed from the `GrandmaVisitorInitiator`, we grab the reference of what React elements are meant to be there.

Then, we generate a _new sub-tree_ with `genTypeExpression` (we’ll look at that in a moment).

Finally, we _replace_ the current node (the `"👵"` string) with _the new sub-tree_ using `replaceWith`. In other words, we’re performing a transform like this:

![a hand-written diagram showing the AST transform of ‘👵’ to a React expression](https://dev-to-uploads.s3.amazonaws.com/i/4pskg05is60a1zofr5qg.png)

_(The most ridiculous diagram I think I’ve ever drawn)_

### Generating Sub-Trees (Type Expressions)

In the previous step we created a whole new sub-tree of our AST using `genTypeExpression`. Babel lets us generate these AST sub-trees using type expressions — let’s dig into how create these using the `types` builders that Babel provides:

```javascript
module.exports = function ({ types: t }) {
  const genTypeExpression = (node) => {
    return t.callExpression(
      t.memberExpression(t.identifier("React"), t.identifier("createElement")),
      [
        /^[A-Z]/.test(node.type[0])
          ? t.identifier(node.type)
          : t.stringLiteral(node.type),
        t.objectExpression(node.args),
        ...node.children.map(genTypeExpression),
      ]
    );
  };

  const GrandmaVisitor = { /* ... */ };
  const GrandmaVisitorInitiator = { /* ... */ };
  
  return { visitor: GrandmaVisitorInitiator };
};
```

By calling e.g. `t.callExpression` we create a `CallExpression` node. Similarly, to create a `StringLiteral` we would call `t.stringLiteral`.

To build a particularly complex tree, I would strongly recommend first using something like [AST explorer](https://astexplorer.net/) to get the AST node names, and then find the corresponding builder in [the babel-types documentation](https://babeljs.io/docs/en/next/babel-types.html), so you know which arguments are needed.

## Summary

You should now know:

*   What Babel does (parse, transform, generate)
*   To use [AST explorer](https://astexplorer.net/) to view code’s corresponding AST
*   How to create a visitor to traverse and modify an AST

And that’s it! Go forth and create your own Babel plugins! You could turn [CSS comments into real CSS](https://github.com/styled-components/babel-plugin-styled-components), transpile [import statements to deferred require statements](https://github.com/airbnb/babel-plugin-dynamic-import-node), or maybe… just maybe… make a stupid alternative to JSX that depends heavily on the 👵 emoji.

{% twitter 1246887081277902852 %}