Build your own Babel-Plugin from scratch
A brief introduction
First of all, let's talk about Babel, Babel is a transpiler which converts code from JavaScript to JavaScript, maybe you are a little bit confused, but let's take the classic JSX example. When you code a React application you are not writing standard JS, and Babel is the one who translates all that beautiful code into some JS that your browser can understand.
Well, all of this is pretty cool, now it's time to talk about how it works, it's really simple, to be honest, it's just a visitor pattern which is applied in every AST (AST is the Abstract Syntax Tree generated after processing your input code) node. This pattern allows us to effectuate some actions like modifying this AST before generating the new code.
A simple example
Nowadays is really common to hear about CSS-in-JS tools, like Styled-Components, or Styled-JSX. So let's create a simple CSS extractor, as requirements we are gonna make the assumption that all the styles must be declared in a function called componentStyle if we are talking about a non-stateless component.
The way we are gonna implement it is by creating custom JSX-tags that are gonna be listed as variables inside this function and associated with an object containing the desired style, here we have a simple example of a component defining the tags and their associated styles, as a convention to make it easier the tags are going to be called <STYLED_>+<HTML tag>.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Foo extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("mounted");
}
componentStyle() {
STYLED_DIV = {
'color': 'red',
'position': 'relative',
'background-position': 'center'
};
STYLED_SPAN = {
'color': 'black',
'background-color': 'red'
};
}
render() {
return (
<STYLED_DIV>
<STYLED_SPAN> Hi! </STYLED_SPAN>
</STYLED_DIV>)
}
}
Now that we have decided on our example to follow we need to make it works as expected, our goal is to:
- Extract CSS from JS
- Generate CSS files
- Replace tags from JS with standard tags and associate them with CSS styles
First of all let's define our visitor function, as a plugin we are required to export a default function that returns an object with the field visitor in which we have defined the callbacks associated with each AST node we want to modify or to effectuate any action on it. In the next code, we can appreciate the visitor that we are going to apply and a little description of what are we going to do in each case.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
exports.default = function (_ref) {
var t = _ref.types;
return {
visitor: {
Program: {
exit(path, state) {
/*
At the end we want to write the CSS files
*/
}
},
Class: {
enter(path, state) {
/*
When we enter a Class node we want to check if this Class has custom tags
and generate the CSS
*/
}
},
ClassMethod: {
enter(path, state) {
/*
If the method is componentStyle we want to remove it because we have already generated
the CSS code
*/
}
},
JSXElement: {
enter(path, state) {
/*
If this is a custom JSXElement we want to replace it by a standard JSXElement
*/
}
}
}
}
};
I'm not going to write all the logic needed in each case because it's really simple, you can check the final work HERE. But basically, that's how we create a new plugin that extracts the CSS from JS, and allows us to use custom tags, I've also added in that repo an implementation to stateless components where you take the styles from the parameters when you call the function, so there is another hook on the visitor over the CallExpression node.
Now you just need to hook it as any other plugin on your .babelrc and you will be processing your code with your own plugin, allowing you to create new syntax and a whole new world, or a whole new set of problems, haha.
Conclusions
- You don't need to be an expert to create a new Babel plugin and make your own syntax extension.
- Things are not so magical as they seem to be, it's just knowing how Babel, Webpack, and other tools work. A funny way to do it is by creating a plugin for example.