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:
- Set up vscode to be able to debug the plugin.
- 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.
- Read other plugins' code.
- Install
@types/babel__core
. The autocomplete (even in JS) in vscode is lit. Really helpful! - 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.
- 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.
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
norAssignmentExpression
). 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 ofvue-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!
Top comments (3)
I wrote my first babel plugin here github.com/sanketmaru/babel-plugin....
Couple of issues i am unable to proceed with and wanted help
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.
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).
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 forlast version of chrome, firefox and safari
, and maybe that's where your bug is.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).
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.