Babel is a source to source compiler for JavaScript language. It can also perform static analysis of source code, such as analyzing the dependency tree of source code.
A small problem using console.log
When debugging JavaScript, you often need to use console.log to print the values of certain variables or expressions when the application executes a certain line of code. The values of certain variables or expressions are often printed in multiple places. This will cause the browser or terminal to output a lot at once, and sometimes it is difficult to distinguish where it is printed. The browser debugging tool will give the code file and row and column information, but we will not record the row and column information of console.log. Therefore, the following writing method is often used to identify:

console.log(“data”, data)
console.log(“1”, data)
Of course, using breakpoint debugging tools can also be very convenient to observe the values of certain variables or expressions during operation, but it is a bit overkill and cumbersome, so many people are still accustomed to using console.log.

So it would be great if there was a tool that automatically converts console.log(a) into console.log(“a”, a). It is easy to think of writing a Babel plug-in to solve this problem.

Get started quickly with Babel plug-in
Some simple code conversion plug-ins are relatively simple, and writing Babel plug-ins is also very easy to get started, so there is no need to be afraid.

Abstract Syntax Tree (AST)
Abstract syntax tree (AST) is a tree used to describe the abstract syntax structure of source code.

The @babel/parser package, formerly known as babylon, is used to complete the process of parsing source code into AST and follows the Babel AST Spec. You can use the online tool ASTExplorer to get a feel for abstract syntax trees. This tool is often used when writing plug-ins.

Babel compilation process
The Babel compilation process is roughly as follows:

Parse: Parse the source code into an abstract syntax tree, involving static analysis (Lexical Analysis) and syntactic analysis (Syntactic Analysis);
Transform: perform various operations on the abstract syntax tree;
Generate (Generate: generate code from the converted abstract syntax tree;
The Babel plugin only runs during the conversion process.

Traverse
To perform various operations on the abstract syntax tree involves traversing the abstract syntax tree, visiting a certain node, and performing various operations on the node.

Visitor
Visitor pattern is an abstract syntax tree traversal pattern.

For example, the following definition:

const visitor = {
Identifier () {
console.log(‘called’)
}
}
Will only work on Identifier type nodes.

Paths
Path is an object used to locate nodes in the tree. The first parameter of the Visitor method is a Path:

const visitor = {
Identifier (path) {
console.log(path)
}
}
The path object contains node information, operation methods of various nodes, etc.

@babel/types
The @babel/types package contains methods for determining and constructing nodes of various types.

babel-plugin-log
Here we only complete the operation of converting console.log(a) to console.log(“a”, a). The complete source code can be viewed in the code repository babel-plugin-log.

In addition to reading the babel-handbook tutorial when writing a babel plug-in, it is a good way to refer to the source code of other babel plug-ins. For example, I referred to babel-plugin-lodash, especially the testing method.

Source code
src/index.js

// console.log(a) is a CallExpression node, but CallExpression contains more than just console.log(a)
// So here we need a method to determine whether the node is called by console.log(a). You can use the online tool astexplorer.net
// Observe and analyze and use the following code to judge
function isConsoleLogCall (path) {
const { callee } = path.node
const {
type,
object,
property
} = callee
return type === ‘MemberExpression’ &&
(object.type === ‘Identifier’ && object.name === ‘console’) &&
(property.type === ‘Identifier’ && property.name === ‘log’)
}

// 1. types are @babel/types
// 2. The return value is a visitor object
module.exports = function ({ types: t }) {
return {
CallExpression (path) {
//The conversion we need to perform is to insert the parameter name string before each call parameter.
const args = path.node.arguments
const newArgs = []
args.forEach(arg => {
newArgs.push(t.stringLiteral(arg.name), arg)
})
path.node.arguments = newArgs
}
}
}
test
The main purpose of testing is to test whether the generated code matches the expected code. Here we refer to the test writing method of babel-plugin-lodash and use the Jest framework.

The files are as follows:

test/
fixtures/
one-identifier
actual.js
expected.js
multiple-identifiers
actaul.js
expected.js
index.spec.js
The main thing is to compare whether the converted code of actual.js is the same as the content of the expected.js file. Two points need to be noted:

The code generated by babel contains a semicolon at the end of the sentence;
Remove whitespace characters before and after code before comparison
index.spec.js

/* global describe, test, expect */
const _ = require(‘lodash’)
const fs = require(‘fs’)
const glob = require(‘glob’)
const path = require(‘path’)
const { transformFileSync } = require(‘@babel/core’)
const plugin = require(‘../src’)

function getTestName (testPath) {
return path.basename(testPath).split(‘-‘).join(‘ ‘)
}

describe(‘babel-plugin-log’, () => {
const testPaths = glob.sync(path.join(__dirname, ‘fixtures/*/’))

for (const testPath of testPaths) {
const testName = getTestName(testPath)
const actualPath = path.join(testPath, ‘actual.js’)
const expectedPath = path.join(testPath, ‘expected.js’)
test(`should work with ${testName}`, () => {
const expected = fs.readFileSync(expectedPath, ‘utf8’)
const actual = transformFileSync(actualPath, {
plugins: [[plugin]]
}).code

expect(_.trim(actual)).toBe(_.trim(expected))
})
}
})
Summarize
The above only contains the most introductory stuff. Before actually writing a plug-in, you need to understand the concepts and best practices such as Scope and State in babel-handbook. You can also refer to the source code of other babel plug-ins.