Writing a Babel plugin to convert arrow functions to function expressions

2 min read - posted on 20 Nov 2018

I'm still (slowly) learning how to work with ASTs and tools that use them. Today I wanted to learn how Babel takes arrow functions like:

const log = (message) => console.log(message);

and converts it into ES5 function expressions like:

const log = function (message) {
console.log(message);
};

Hilariously, Acorn (the JavaScript parser that Babel uses) comes with a built-in arrowFunctionToExpression method!

If we head over to astexplorer.net and type in the code that we want to transform, we get an Abstract Syntax Tree like:

{
"type": "File",
"start": 0,
"end": 45,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 44
}
},
"program": {
"type": "Program",
"start": 0,
"end": 45,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 44
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "VariableDeclaration",
"start": 1,
"end": 45,
"loc": {
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 2,
"column": 44
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 7,
"end": 44,
"loc": {
"start": {
"line": 2,
"column": 6
},
"end": {
"line": 2,
"column": 43
}
},
"id": {
"type": "Identifier",
"start": 7,
"end": 10,
"loc": {
"start": {
"line": 2,
"column": 6
},
"end": {
"line": 2,
"column": 9
},
"identifierName": "log"
},
"name": "log"
},
"init": {
"type": "ArrowFunctionExpression",
"start": 13,
"end": 44,
"loc": {
"start": {
"line": 2,
"column": 12
},
"end": {
"line": 2,
"column": 43
}
},
"id": null,
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 13,
"end": 20,
"loc": {
"start": {
"line": 2,
"column": 12
},
"end": {
"line": 2,
"column": 19
},
"identifierName": "message"
},
"name": "message"
}
],
"body": {
"type": "CallExpression",
"start": 24,
"end": 44,
"loc": {
"start": {
"line": 2,
"column": 23
},
"end": {
"line": 2,
"column": 43
}
},
"callee": {
"type": "MemberExpression",
"start": 24,
"end": 35,
"loc": {
"start": {
"line": 2,
"column": 23
},
"end": {
"line": 2,
"column": 34
}
},
"object": {
"type": "Identifier",
"start": 24,
"end": 31,
"loc": {
"start": {
"line": 2,
"column": 23
},
"end": {
"line": 2,
"column": 30
},
"identifierName": "console"
},
"name": "console"
},
"property": {
"type": "Identifier",
"start": 32,
"end": 35,
"loc": {
"start": {
"line": 2,
"column": 31
},
"end": {
"line": 2,
"column": 34
},
"identifierName": "log"
},
"name": "log"
},
"computed": false
},
"arguments": [
{
"type": "Identifier",
"start": 36,
"end": 43,
"loc": {
"start": {
"line": 2,
"column": 35
},
"end": {
"line": 2,
"column": 42
},
"identifierName": "message"
},
"name": "message"
}
]
}
}
}
],
"kind": "const"
}
],
"directives": []
},
"comments": []
}

When making Babel plugins or anything that parses ASTs we use a Computer Science concept called the Visitor Pattern. Basically, we pick the "type" of node we want to grab onto and then do operations on it. In our case we can our app goes File -> Program -> VariableDeclaration -> ArrowFunctionExpression! Perfect, we'll grab ArrowFunctionExpression.

export default function (babel) {
const { types: t } = babel;

return {
visitor: {
ArrowFunctionExpression(path) {
return path.arrowFunctionToExpression();
},
},
};
}

Most of that is just boilerplate. The important bit is that we hook into all ArrowFunctionExpression's and call arrowFunctionToExpression() on them. That results in the following output:

const log = function (message) {
return console.log(message);
};

Perfect! I promise I'll try something more challenging next time :)


Subscribe to my email list!

Let me be real with you. Sometimes when I'm bored I log in to Buttondown and look at the audience number. If it's bigger than it was the week before, well that makes me feel really happy!

I promise I'll never spam you and I will, at most, send you a monthly update with what's happening on this site.