Babel is a transcoder that is currently used in the React and Vue projects. It can convert es6+ syntax to ES5, JSX syntax, etc. In fact, it can do any conversion in the form of a custom plug-in. We convert specific code in our projects by configuring plug-ins and presets (collections of multiple plug-ins), such as env, stage-0, etc. How these libraries are implemented is explored with a small example — converting es6 classes to ES5.

Article Structure:

  • Webpack environment configuration
  • How to write a Bable plug-in
  • How do I convert a class from ES6 to ES5
  • Optimization of inheritance
  • Packaged code

Webpack environment configuration

Babel-core is a loader that provides the core Api of Babel. We do all the code conversion through plug-ins. Next, we will implement an ES6-class transformation plug-in without using a third-party plug-in. To initialize a project, perform the following steps:

  • npm install webpack webpack-cli babel-core -D
  • Create a new webpack.config.js
  • Configuration webpack. Config. Js

If we want our plug-in to be named transform-class, we need to do the following in our WebPack configuration:

Next, create a new babel-plugin-transform-class folder in node_modules to write the plugin logic (if this is a real project, you will need to write the plugin and publish it to the NPM repository), as shown in the figure below:

The red area is my new folder. Above it is a standard plugin project structure. To make it easier for my plugin to write only the core index.js file.

How to write a Bable plug-in

The Babel plug-in is actually implemented through an AST(Abstract Syntax tree). Babel helps us convert JS code to AST, then allows us to modify it, and finally convert it to JS code again. So there are two questions: what is the mapping between the JS code and the AST? How do I replace or add an AST?

Ok, let’s start with a tool:astexplorer.net:

This tool converts a piece of code into an AST:


One more document:babel-types:

This is the Api document for creating the AST node. For example, if we want to create a class, we first go to astExplorer.net and discover that the AST type corresponding to the class is ClassDeclaration. Ok, we go to the documentation and find that we can create such a node by calling the following API:

Let’s get started on actually writing a plug-in, in the following steps:

  • Export a function in index.js
  • Function returns an object that has a visitor parameter (must be called visitor)
  • Query through astexplorer.netclassThe corresponding AST node isClassDeclaration
  • Set up a capture function in VistorClassDeclaration“, which means I want to capture all of the js codeClassDeclarationnode
  • Write the logical code to complete the transformation

The above steps correspond to the code:

module.exports = function ({ types: t }) {
    return{visitor: {ClassDeclaration(path) {// Complete the transformation here}}}; }Copy the code

The first {types:t} thing is the variable t that is deconstructed from the arguments. This is actually the t in the babel-types document (red box below). We use this t to create the node:

The second parameter, path, is the captured information about the node. We can obtain the AST of this node through path.node, and modify it based on this to achieve our goal.

How do I convert an ES6 class to an ES5 class

The above is all preparatory work, the real logic from now on, we first consider two questions:
  1. We will do the following conversion. First, we will convert es6 classes to es5 class writing (i.e., normal functions). We will observe that a lot of code can be reused, including function names, code blocks inside functions, etc.

  1. If you do not define theconstructorMethod, the JavaScript engine automatically adds an empty one for itconstructor()Method, which requires us to do compatibility processing.

Next, let’s start writing code. The idea is:
  • Get the old AST node
  • Create an array to hold the new AST node (although the original class is just one node, it will be replaced by several function nodes)
  • Initialize the defaultconstructorNode (as mentioned earlier, constructor may not be defined in the class)
  • The AST object that loops through the old node (loops out several function nodes)
  • Check whether the node type is correctconstructorIf so, create a normal function node from the old data and update the defaultsconstructornode
  • Deal with the rest notconstructorIs created from the old dataprototypeType of function, and putes5FnsIn the
  • So that’s the end of the loopconstructorThe nodes are also placed ines5FnsIn the
  • Check whether the length of es5Fns is greater than 1. If yes, usereplaceWithMultipleThis API updates the AST
module.exports = function ({ types: t }) {
    return{visitor: {ClassDeclaration(path) {// Get the old AST nodelet node = path.node
                let className = node.id.name
                letClassInner = Node.body. Body // Create an array to hold the newly generated ASTletEs5Fns = [] // Initialize the default constructor nodelet newConstructorId = t.identifier(className)
                let constructorFn = t.functionDeclaration(newConstructorId, [t.identifier(' ')], t.blockStatement([]), false.false) // Loop through the AST object of the old nodefor (let i = 0; i < classInner.length; i++) {
                    letItem = classInner[I] // determine whether the function type is constructorif (item.kind == 'constructor') {
                        let constructorParams = item.params.length ? item.params[0].name : []
                        let newConstructorParams = t.identifier(constructorParams)
                        let constructorBody = classInner[i].body
                        constructorFn = t.functionDeclaration(newConstructorId, [newConstructorParams], constructorBody, false.false} // Handle the remaining nodes that are not constructorelse {
                        let protoTypeObj = t.memberExpression(t.identifier(className), t.identifier('prototype'), false)
                        let left = t.memberExpression(protoTypeObj, t.identifier(item.key.name), false) // Define the right-hand side of the equal signlet prototypeParams = classInner[i].params.length ? classInner[i].params[i].name : []
                        let newPrototypeParams = t.identifier(prototypeParams)
                        let prototypeBody = classInner[i].body
                        let right = t.functionExpression(null, [newPrototypeParams], prototypeBody, false.false)
                        let protoTypeExpression = t.assignmentExpression("=", left, right) es5fns. push(protoTypeExpression)}} // Loop ends, Es5Fns. Push (constructorFn) // Determine whether the constructorns length is greater than 1if (es5Fns.length > 1) {
                    path.replaceWithMultiple(es5Fns)
                } else {
                    path.replaceWith(constructorFn)
                }
            }
        }
    };
}

Copy the code

Optimization of inheritance

Bird.prototype = object.create (Parent); if so, you need to add a new line of code, including the super keyword.

Packaged code

npm start
class

At the end

Now a class converter is finished, hopefully to help you understand Babel a little bit.

Refer to the content

Github – Babel Plugin Development Guide Babel-types