preface

In 2021, the build tools have gone from webPack to Vite, Snowpack and other new tools. But no matter which build tool you use, Babel is still necessary: On the one hand, more and more projects are using typescript syntax, and TS needs to be compiled into javascript to run in browsers and Nodes. On the other hand, we need Babel to compile into browser-supported code some new language features that are not widely supported by some browsers.

But Babel is both familiar and unfamiliar to the average front-end practitioner, and while we use Babel functionality every day, most of the time I use Babel plugins directly. When we have a need to capture or modify code structure at compile time, how do we use the Babel tool to do so?

This article will show you how to get started with Babel compilation quickly with an example.

Demand is introduced

Deer’s business warehouse uses the React framework for development. Due to component upgrade, it is necessary to upgrade all Viewer components to View components, and add a className: new-view to the modified components.

Due to the large amount of code in the current business warehouse, manual modification may take a lot of time and energy, and the correctness of modification cannot be guaranteed. Now Fawn decides to write a conversion script to implement the upgrade.

prepared

First, here are a few Babel packages.

@babel/parser

IO /docs/en/bab… This package parses the code text into an AST (Abstract Syntax Tree)

require("@babel/parser").parse("code", {
  // parse in strict mode and allow module declarations
  sourceType: "module".plugins: [
    // enable jsx and flow syntax
    "jsx"."flow",]});Copy the code

@babel/traverse

IO /docs/en/bab… This package traverses the AST nodes and updates the AST node information.

import * as parser from "@babel/parser";
import traverse from "@babel/traverse";

const code = `function square(n) { return n * n; } `;

const ast = parser.parse(code);

traverse(ast, {
  enter(path) {
    if (path.isIdentifier({ name: "n" })) {
      path.node.name = "x"; }}});Copy the code

@babel/types

IO /docs/en/bab… This package provides Babel Ast type determination and various type creation functions.

@babel/generator

IO /docs/en/bab… This package will generate code text from the AST, as opposed to @babel/ Parser.

import { parse } from "@babel/parser";
import generate from "@babel/generator";

const code = "class Example {}";
const ast = parse(code);

const output = generate(
  ast,
  {
    /* options */
  },
  code
);
Copy the code

Begin to implement

Convert the AST

First, we need to parse the repository code into the AST. Since our code uses the React framework, we need to add the JSX plug-in when converting.

const fs = require('fs');
const parser = require('@babel/parser').default;
const content = fs.readFileSync('Here's the file address'.'utf-8');
const ast = parser.parse(content, {
	sourceType: "module".plugins: [
		'jsx']});Copy the code

Modify the node

We need to traverse the ast nodes with @babel/traverse, and then transcode by modifying the AST nodes. So how do you iterate and how do you modify?

On traverse, we need to define vistor methods that will be called when the tool traverses the same type of method we defined.

traverse(ast, {
  FunctionDeclaration: function(path) {
    path.node.id.name = "x"; }});Copy the code

For example, in the above example, if a node is of type FunctionDeclaration, the above method is called when traversing the node, setting the node’s name to x.

As a novice, it can be difficult to find the type of node you need to change all at once. Here is an online parsing tool: astexplorer.net/.

Post the example code to the edit view and set @babel/ Parser as the parser, and the AST structure that it parses is displayed in the right column. After selecting the node we want to modify, the AST on the right is automatically highlighted. Based on the structure on the right, let’s write the conversion code.

We select the Viewer import statement and can see that the Viewer we need to modify is in aImportDeclarationNode, itssourceThe value ofsome-view.

traverse(ast, {
  ImportDeclaration: function(path) {	/ / the import statement
    const node = path.node;	// Path. node is the current node
	if (node.source.value === 'some-view') {
	  node.specifiers = node.specifiers.map(s= > {
	  	if (s.imported.name === 'Viewer') {	/ / find the Viewer
			return t.importSpecifier('View'.'View');	/ / create a importSpecifier node using types: https://babeljs.io/docs/en/babel-types#importspecifier
		}
		returns; }); }}});Copy the code

After converting the dependencies, we then transform the nodes in JSX. Again, select the code we need to update on the left and look at the structure on the right. Since the requirements also need to add a className, we also need to add a className in the demo to help us locate the node.

We need to modify the node in oneJSXOpeningElementBelow, the node type isJSXIdentifier, the className inattributesIn the.

traverse(ast, {
  ImportDeclaration: function(path) {	/ / the import statement
    const node = path.node;	// Path. node is the current node
	if (node.source.value === 'some-view') {
	  node.specifiers = node.specifiers.map(s= > {
	  	if (s.imported.name === 'Viewer') {	/ / find the Viewer
			return t.importSpecifier('View'.'View');	/ / create a ImportSpecifier node using types: https://babeljs.io/docs/en/babel-types#importspecifier
		}
		returns; }); }},JSXOpeningElement: function(path) {
  	const node = path.node;
	if (node.name.name === 'Viewer') {	// The label name is Viewer
	  node.name.name = 'View';
	  node.name.attributes.push(t.jsxAttribute('className'.'new-view'));	/ / create a JSXAttribute node using types: https://babeljs.io/docs/en/babel-types#jsxattribute}}});Copy the code

In actual processing, you also need to consider the presence of a className on the Viewer component, which is not expanded here. In addition, we also need to handle the Viewer inside the closed tag, which is the same method as JSXOpeningElement.

The output text

const generate = require('@babel/generator').default;
const newContent = generate(ast, {}, content);
Copy the code

At the end

The examples of this article have been explained, need to help everyone’s work and study ~ bow.