I wrote my first babel plugin! (And it wasn't that hard!)
Tags: babel, vue, javascript
Published: 2019-03-05 22:35:58

> Cover image from https://www.grimms.eu/en/products/building-amp-rainbow-worlds/organic-shapes/1240/colored-waldorf-blocks

---

Today I wrote a tiny babel plugin for reducing the size of a vue proyect by transforming the `props` attribute in their minimal expression (and at the same time, removing `vue-types` as a dependency).

### Steps I did to learn how to write a babel plugin

To understand (at least something, lol) how babel's plugin system works, I did the following:

1. Set up vscode to be able to debug the plugin.
2. Read the plugin section of the babel-handbook: https://github.com/jamiebuilds/babel-handbook. I didn't read it all, though. And the first time didn't understood anything.
3. Read other plugins' code.
4. Install `@types/babel__core`. The autocomplete (even in JS) in vscode is lit. Really helpful!
5. Debug a lot. In conjunction with the handbook, it made me understand a little how to understand how the code is interpreted and how to modify it.
6. Add jsdoc everytime you can. You help vscode to help you ;)

The following snipet is the vscode launch's configuration for debugging a babel plugin:

```json
{
  "type": "node",
  "request": "launch",
  "name": "Debug babel",
  "console": "integratedTerminal",
  "autoAttachChildProcesses": true,
  "program": "${workspaceFolder}/node_modules/@babel/cli/bin/babel.js",
  "args": [
    "--config-file=${workspaceFolder}/babel.config.js",
    "${workspaceFolder}/path/to/file.js"
  ]
}
```

The string `"${workspaceFolder}/path/to/file.js"` is the file to be compiled.

### Babel plugin basic structure

```js
const { declare } = require('@babel/helper-plugin-utils');
const { types: t } = require('@babel/core');

module.exports = declare(api => {
  // If the plugin requires babel 7++
  api.assertVersion(7);

  return {
    // For what I learned, although the name is not required,
    // if you add one, remember to NOT add the "babel-plugin"
    // prefix. E.g., if the package's name is
    // "babel-plugin-transform-vue-props", the name would be
    // the following:
    name: 'transform-vue-props',
    visitor: {
      /**
       * @param {babel.types.ImportDeclaration} path Import's node
       * @return {void}
       */
      ImportDeclaration(path) {
        if (path.node.source.value === 'vue-types') {
          path.remove();
        }
      },
    },
  };
});
```

The `visitors` prop its where everything happens.

> When we talk about "going" to a node, we actually mean we are visiting them.
>
> [Handbook: Visitors](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-visitors)

Each node has a type, and everyone of them can be visited. In the example above we are visiting each import declaration and remove them if they are importing the `vue-types` library.

### How to transform code

By the way, if you want to transform, e.g. an object, into an array of strings (the keys), you would have to do the following:

Consider this code:

```js
const obj = {
  name: 'Luciano',
  age: 28,
};
```

If you want to transform it to this:

```js
const obj = ['name', 'age'];
```

You would have to do the following:

```js
const { declare } = require('@babel/helper-plugin-utils');
const { types: t } = require('@babel/core');

module.exports = declare(() => {
  return {
    name: 'transform-obj-to-array',
    visitor: {
      /**
       * @param {babel.types.VariableDeclarator} path Declaration
       * @return {void}
       */
      VariableDeclarator(path) {
        const node = path.node;

        if (!t.isObjectExpression(node.init)) {
          return;
        }
        node.init = t.arrayExpression(
          node.init.properties.map(prop => t.stringLiteral(prop.key.name)),
        );
      },
    },
  };
});
```

> By the way, I had to debug it until I could find the correct visitor (wasn't `VariableDeclaration` nor `AssignmentExpression`). JSDoc + `@types/babel__core` + VSCode FTW.

As you can see, isn't as simple as to replace it like a string. The `types` (aka `t`) prop from `@babel/core` it's very helpful for validating what structure is something and for building new ones.

### babel-plugin-transform-vue-props

- The lib can be found here https://github.com/lgraziani2712/babel-plugin-transform-vue-props
- It _really_ helps to remove `vue-types` as dependency (since it does nothing in production), which can be weight between 25kb~ to 3.5kb~ gzipped and if webpack (or any bundler) is configured to use the production file (https://github.com/dwightjack/vue-types#production-build). This size doesn't counts the use of `vue-types` in every component.

## Motivation

I _reaaaally_ love to resolve optimization problems, and I wanted to remove `vue-types` from the production bundle. I searched everywhere but didn't find anything confortable to use. I also rediscovered this doc https://vuejs.org/v2/guide/components-props.html#Prop-Types and remember what is the most simple definition of the `props` attribute of a component.

EDIT: I just found https://astexplorer.net/. Is really dope!

---

I hope this post will motivate anyone who wants to explore the world of babel plugins but don't know where or how to start! Cheers!