preface

This is not an in-depth article on Babel, but rather a demos for beginners; This demos won’t cover a whole bunch of Babel’s awesome features. Instead, there are a bunch of demos explaining how to start the BabelPlugin from scratch and then develop a beggar beggar version of BabelPluginImport and plug it into webPack

5 minutes reading, 5 minutes Demo Coding What can you learn?

  • Write your first Babel Plugin
  • Implement the WebPack Resolve alias feature using the Babel Plugin
  • Implement beggar beggar version BabelPluginImport
  • Plug your own plug-ins into Webpack

STEP 1 | meditation

Consider the implementation of Babel in several steps:

  1. Js files should be passed to Babel as strings
  2. Babel parses the string to produce the AST
  3. The AST should probably be JSON, and what happens when es6 is converted to ES5 is called conversion
  4. The AST must also be output as a String, which is called generation

STEP 2 | a profound

Write your first Babel Plugin

Plug-in development for Babel is available in the Babel plug-in manual

So let’s do the simplest demo

Follow the steps in STEP 1

// babel.js

var babel = require('babel-core'); const _code = `a`; const visitor = { Identifier(path){ console.log(path); console.log(path.node.name); }}; babel.transform(_code, { plugins: [{ visitor: visitor }] });Copy the code
Are there any questions after watching this demo?
  • Problem 1. Plugins are passed in the [{visitor: {}}] format
  • Question 2. Why is the hook function called Identifier instead of Id? The name?
  • Question 3. Actually similar to question 2, how to define hook function, how to define, what specification?

Problem solving

  • Question 1: This is what the Babel Plugin definition requires, and we don’t worry about it
  • In question 2, the so-called hook function is of course related to the life cycle and so on. The hook function here is actually the Babel’s hook function in the process of parsing, such as Identifier, when the Identifier is parsed into the hook function
  • @babel/types[API] hook functions can be defined by @babel/types[API]

Ok, we have no problems with this simple demo. Then we will execute the demo: node babel.js and output the following path AST:

// Because light is one"a"The AST file is also 284 lines long, so I won't print it all out. Give only the node that represents the current Identifier in the AST object.type: 'Identifier',
	start: 0,
	end: 1,
	loc: SourceLocation {
		start: [Position],
		end: [Position],
		identifierName: 'a'
	},
	name: 'a'
},
Copy the code

From this AST node, you can get a sense of the AST. The node stores the current LOC information, as well as the name of the identifier

STEP 3 | implement resolve alias

preface

After a little experimentation, familiarize yourself with the API of @babel/types. After familiarizing yourself with a few apis, you can start simple development. In this section we’ll look at ImportDeclaration

Implement the WebPack Resolve alias feature using the Babel Plugin

Consider the steps to implement the Resolve Alias:

  1. _code=”import homePage from ‘@/views/homePage’; ;
  2. Const alias = {‘@’: ‘./’};
  3. /views/homePage’ into ‘./views/homePage’ output

Summary good we want to achieve the function, the following use demo to achieve again

// babel.js

const babel = require('babel-core');
const _code = `import homePage from '@/views/homePage'; `; constalias = {
    The '@': '/'
};

const visitor = {
    ImportDeclaration(path){
        for(let prop in alias) {if(alias.hasOwnProperty(prop)){
                let reg = new RegExp(`${prop}/ `); path.node.source.value = path.node.source.value.replace(reg,alias[prop]); }}}}; const result = babel.transform(_code, { plugins: [{ visitor: visitor }] }); console.log(result.code);Copy the code

This demo replaces @ in path.node.source.value with./ when entering the ImportDeclaration hook function.



Indicates that our alias is in effect

STEP 4 | beggar beggar version BabelPluginImport is “coming

Question:

Again, think about the difficulty of implementing a BabelPluginImport.Copy the code

I introduced BalbelPluginImport in code splitting for React performance optimization, Import {Button} from ‘antd’ to import {Button} from ‘antd/lib/ Button ‘;

-> Our beggar version of BabelPluginImport simply implements this functionality

// babel.js

var babel = require('@babel/core');
var types = require('babel-types');
// Babel helper functions for inserting module loads
var healperImport = require("@babel/helper-module-imports");

const _code = `import { Button } from 'antd'; `; Const ImportPlugin = {// libraryName libraryName:'antd'// libraryDirectory:'lib'// This queue is used to store the components waiting for helperModuleImports addNamed, but remove and import are done at ImportDeclaration. ToImportQueue: {}, // Use helperModuleImports addNamed to import components with the correct path:function(file){
        for(let prop in this.toImportQueue){
            if(this.toImportQueue.hasOwnProperty(prop)){
                return healperImport.addNamed(file.path, prop, `./main/${this.libraryDirectory}/index.js`); }}}}; const visitor = { ImportDeclaration(path, state) { const { node, hub: { file } } = path;if(! node)return; const { value } = node.source; // Determine the currently resolved importsourceIs it antD? If it is, replace itif (value === ImportPlugin.libraryName) {
            node.specifiers.forEach(spec => {
                if(types.isImportSpecifier(spec)) { ImportPlugin.toImportQueue[spec.local.name] = spec.imported.name; }}); // path.remove removes import {Button} from'antd'; path.remove(); // import adds import _index from to the code'./main/lib/index.js'; ImportPlugin.import(file); }}}; const result = babel.transform(_code, { plugins: [ { visitor: Visitor}, // In addition to the custom visitor, we added a third party transform-es2015-modules-commonjs to convert import to require"transform-es2015-modules-commonjs"]}); console.log(result.code);Copy the code

Output result:








The original code is transformed into the following code

STEP 5 | Demo Coding perfect moment

Highlight moment is coming, said so long theoretical knowledge, you can start to write a.

5.1 Create-react-app Build a project first

npx create-react-app babel-demo
Copy the code

5.2 Simple development project, one entry component app.js, one Button component

// app.js import React from 'React '; // app.js import React from' React '; import Button from 'firefly-ui'; function App() { return ( <div className="App"> <Button /> </div> ); } export default App; // Button.js import React, { Component } from 'react'; Class Button extends Component{render(){return <div> </div>}} export default Button;Copy the code

Ok, the code is done, and when it runs, it crashes, which is fine, but it’s weird when it doesn’t crash, because you don’t have firefly-UI, but what is firefly-UI? Firefly-ui is the firefly-UI directory of your SRC directory, so you need to write a plugin to solve this problem.

  • This import is converted when resolved to import {Button} from ‘firefly-ui’
  • Use the transformed import above when parsing to Button in JSX

So let’s start with these two Babel import

5.3 NPM run eject To eject the WebPack configuration

Ok, why eject configuration, because you need to configure babel-loader plugins. // find webpack.config.js -> find babel-loader -> find plugins // Note: The import plugin is placed in the SRC sibling folder babel-plugins import.js // so the path here is.. /babel-plugins/import, because the default is node_modules, // timestamp, because webpackDevServer cache, Resolve ('../babel-plugins/import'), {libName: 'firefly- UI ', libDir: 'lib', timestamp: +new Date},] Plugins for balbel-loaderCopy the code

5.4 Import Plugin development

With all the configuration done, we still need to implement… /babel-plugins/import.js

const healperImport = require("@babel/helper-module-imports");

letImportPlugin = {// Read libName and libDir libName from webpack configuration into Program hook function:' ',
    libDir: ' '// Helper-module-imports imports are imported here: toImportQueue: [] // Helper-module-imports imports are imported here: {}, // helper-module-imports replaces the original import import:function(path, file){
        for(let prop in this.toImportQueue){
            if(this.toImportQueue.hasOwnProperty(prop)){
                // return healperImport.addNamed(file.path, prop, `./${this.libName}/${this.libDir}/${prop}.js`);
                let imported = healperImport.addDefault(file.path, `./${this.libName}/${this.libDir}/${prop}.js`);
                this.importedQueue[prop] = imported;
                returnimported; }}}}; module.exports =function ({ types }) {
    return{visitor: {// Program hook function receives webPack configuration Program: {Enter (path, {opts = {}}) {importplugin. libName = opts.libname; ImportPlugin.libDir = opts.libDir; ImportDeclaration: {enter(path, state){const {node, hub: { file } } = path;if(! node)return;
                    const { value } = node.source;
            
                    if(value === ImportPlugin.libName) { node.specifiers.forEach(spec => { ImportPlugin.toImportQueue[spec.local.name] = spec.local.name; }); path.remove(); ImportPlugin.import(path, file); }}}, // The Identifier is used to parse the JSX Button and convert it to the new Identifier(path){if(ImportPlugin.importedQueue[path.node.name]){
                    path.replaceWith(ImportPlugin.importedQueue[path.node.name]);
                }
            }
        }
    }
}
Copy the code

I spent hours exploring the implementation of this plugin. If you implement the ImportDeclaration hook function instead of the Identifier hook function, you can see that the import Button has been converted, but JSX is still a Button. So it says Button is not defined. The diagram below:

Ok, following the full implementation of my demo, I found that all import and JSX have been converted. And the program runs normally. The diagram below:

This is the end of the article, serious students may still find that there are many questions not answered, later have time to continue to write Babel, because I feel that this article is quite a lot of knowledge for beginners, if there is a problem with setting up the environment, or you cannot write the effect of the plugin example, You can see my Babel-Demo source code, you can consult me if you have any questions