In this tutorial, we will do some basic transforms to some source code, using Babel. Many people find the idea of transforming code scary, and unapproachable, but utilising the power of AST’s (abstract syntax trees), and a set of tools provided to us by Babel, most of the heavy lifting is done for us.
Note: The examples in the article will include code specific to react
, redux
, and react-redux
, but familiarity with these libraries is not necessary for this tutorial.
There is a website called AST explorer, that we can paste our code into and get an AST representation in many formats. This website will be useful for quickly viewing code in AST format, and will be useful when ascertaining which nodes we need to target.
Below, we have a file reducers.js
with a couple of imports, and a default export.
For our first transformation, lets add a new import and export to reducers.js
. We’ll add mice
. To do this we need to:
Here’s how we achieve this:
Let’s break this down. First of all, we call the parser on our code which transforms it from a string to an AST.
const ast = parser(file, {sourceType: 'module'});
Note, since we are using ES6 modules, we need to let the parser know with
{sourceType: ‘module’}
.
Next, we use traverse
to find our relevant nodes. How did we know we needed ExportDefaultDeclaration
and ObjectExpression
? This is where AST explorer comes in handy. Below we’ve pasted our code in on the left panel, and on the right we can view the AST representation of our code.
We have 2 ImportDeclaration
’s, so traverse
will help us iterate over them and save the last one into a variable called lastImport
. We then use insertAfter
to insert the new import after the last import.
// this file is made up of snippets from transform.js
let lastImport;
traverse(ast, {ImportDeclaration(path) {lastImport = path;}
const importCode = `import ${reducerName} from './${reducerName}'`;lastImport.insertAfter(parser(importCode, {sourceType: 'module'}));
To add a property to the default exported object, we will use traverse
to iterate over ObjectExpression
‘s. We only expect there to be one, so we will save its properties using properties = path.parent.declaration.properties
. We can then push our new mice
identifier into the properties
array.
// this file is made up of snippets from transform.js
traverse(ast, {ObjectExpression(path) {properties = path.parent.declaration.properties}})
const id = t.identifier(REDUCER_NAME)properties.push(t.objectProperty(id, id, false, true))
You might be wondering what
t.objectProperty(id, id, false, true)
is? Good question. Since themice
alone does not have enough context, we cannot just callparser
on a string of code like in the last example. Babel would parse it as anIdentifier
instead of aProperty
, leading to issues when re-generating the code. To solve this, we use the@babel/types
package to help the parser understand what we have added to the AST.
Now that we have updated our AST, we can call generate
on it. This will transform the code from an AST back into code in string format. We run prettier on the string, and we end up with code like below:
Next up, we will learn how to use replaceWith
to wrap an identifier in a high order component.
Essentially, we want to go from:
export default Sports;
to:
const mapStateToProps = ({ volleyball, soccer }) => ({volleyball,soccer});
export default connect(mapStateToProps)(Sports);
This consists of two steps.
mapStateToProps
function.Here’s the file we will operate on:
And this is the code to transform it:
First thing to note, since we are parsing JSX this time, we need to let the parser know:
const ast = parser(file, {sourceType: 'module', plugins: ['jsx']});
This time we use traverse
to iterate over the AST and find the ExportDefaultDeclaration
. Once we’ve found it, we store the name of the variable being exported.
const declarationName = exportDefaultPath.node.declaration.name;
Since we know the name of the exported variable, we can now replace the entire default export with new code:
exportDefaultPath.replaceWith(// new code...)
Having transformed the AST, we can run generate
and prettier
on it, and write the file to disk. We end up with:
Learning how to manipulate AST’s will open up many new possibilities to you. With AST’s you could write:
I hope this tutorial will set you on your way to exploring the world of AST’s!