DEV Community

Cover image for I wrote my first babel plugin! (And it wasn't that hard!)
Luciano Graziani
Luciano Graziani

Posted on

I wrote my first babel plugin! (And it wasn't that hard!)

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:

{
  "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

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

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:

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

If you want to transform it to this:

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

You would have to do the following:

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

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!

Top comments (3)

Collapse
 
sanketmaru profile image
Sanket Maru

I wrote my first babel plugin here github.com/sanketmaru/babel-plugin....

Couple of issues i am unable to proceed with and wanted help

  1. I am trying to remove the functions based upon some param and it is not able to hit ClassMethod visitor in production mode, where as in development mode it works.

  2. While debugging in vs code. Sometimes the breakpoints hit and sometimes it doesn't. Have you faced this issue ?

The development/production mode can be altered from build.js ( its a create-react-app).

Collapse
 
lgraziani2712 profile image
Luciano Graziani

Hello Sanket! Happy to know you're making a plugin!

BTW, I am no expert about create react app, but I have to ask, why are you using CRA to make/test the plugin? To me it feels like too much for a babel plugin development.

I do not know either what it does CRA internally in dev mode and prod mode (and I thought CRA wouldn't allow you to use custom babel config O: ahhh, you ejected!). But one thing I saw was the browserlist config! You have one for prod and one for dev. In prod you say you want your app to be able to run in really old browsers (> 0.2% rule), while in dev mode you're saying you want to run it only for last version of chrome, firefox and safari, and maybe that's where your bug is.

While debugging in vs code. Sometimes the breakpoints hit and sometimes it doesn't. Have you faced this issue ?

Yeah, happens a lot: webpack is generating the sourcemaps, and sometimes are lines that won't hit. Again, using CRA to develop a simple plugin is overengineering. If you look into my repo github.com/lgraziani2712/babel-plu... you'll see I only have babel and jest as deps (eslint and prettier too though they are only for linting and formatting).

Collapse
 
sanketmaru profile image
Sanket Maru

Hi Luciano Graziani,
Thanks for your reply.
I will give it a try with your observations. May be its not a problem with plugin and prod/dev config with CRA is an issue.