От компиляции и парсинга AST до написания плагинов для Babel

внешний интерфейс GitHub JavaScript Babel

Я видел некоторые статьи о том, как проводить собеседования и писать плагины для Babel на Nuggets, и недавно я узнал. Ниже приводится резюме после изучения.

Ключевые слова: компиляция и парсинг AST, babel.

Составление и анализ AST

AST[Википедия]: вИнформатикасередина,абстрактное синтаксическое дерево(Abstract Syntax Tри, АСТ), или простосинтаксическое дерево(Синтаксическое дерево), даисходный кодграмматикаАбстрактное представление структуры. это начинается сДеревоФорма производительностиЯзык программированияграмматической структуры, каждый узел дерева представляет собой структуру в исходном коде. Грамматика называется «абстрактной», потому что грамматика здесь не представляет все детали, которые встречаются в реальной грамматике. Например, вложенные скобки неявно присутствуют в структуре дерева и не представлены в виде узлов;if-condition-thenТакой оператор условного перехода может быть представлен узлом с двумя ветвями.

400px-abstract_syntax_tree_for_euclidean_algorithm svg

).一般的,在源代码的翻译和компилироватьв процессе,парсерСоздайте дерево разбора. После создания AST во время последующей обработки, такой какСемантический анализэтапе будет добавлена ​​некоторая информация.

Как использовать AST для разбора функции ast(){} и восстановления после изменений

003

Сделайте три шага:

  • Синтаксический разбор js => синтаксическое дерево
  • Пройтись по дереву (сначала предзаказать глубину) => изменить содержимое дерева
  • создать новое дерево
const esprima = require('esprima');//解析js的语法的包
const estraverse = require('estraverse');//遍历树的包
const escodegen = require('escodegen');//生成新的树的包

let code = `function ast(){}`;
//解析js的语法
let tree = esprima.parseScript(code);
//遍历树
estraverse.traverse(tree, {
    enter(node) {
        console.log('enter: '+node.type);
        }, leave(node){
         console.log('leave: '+node.type);
     }
});
//生成新的树
let r = escodegen.generate(tree);
console.log(r);

004

005

После изменения содержимого дерева

const esprima = require('esprima');
const estraverse = require('estraverse');
const escodegen = require('escodegen');

let code = `function ast(){}`;
let tree = esprima.parseScript(code);
estraverse.traverse(tree, {
    enter(node) {
        if (node.type === 'Identifier') {
            node.name = 'Jomsou';
        }
        // console.log('enter: '+node.type);
        // }, leave(node){
        //  console.log('leave: '+node.type);
     }
});
let r = escodegen.generate(tree);
console.log(r);
//结果
function Jomsou() {
}

Плагин Бабель

1. Стрелочная функция ES6 `let sum = (a, b)=>{return a+b}; преобразование в обычную функцию ES5

001
002

const babel = require('babel-core');//babel核心解析库
const t = require('babel-types');//babel类型转化库

let code = `let sum = (a, b)=>{return a+b}`;
let ArrowPlugins = {
    //访问者模式
    visitor: {
        //捕获匹配的API
        ArrowFunctionExpression(path){
            let {node} = path;
            let body = node.body;
            let params = node.params;
            let r = t.functionExpression(null, params, body, false, false);
            path.replaceWith(r);
        }
    }
}
let d = babel.transform(code, {
    plugins: [
        ArrowPlugins
    ]
})
console.log(d.code);

Стрелочная функция записывает преобразование let sum = (a, b)=>a+b;

let babel = require('babel-core');
let t = require('babel-types');

let code = `let sum = (a, b)=>a+b`;

//.babelrc
let AllowPlugins = {
    visitor: {
        ArrowFunctionExpression(path){
            let node = path.node;
            let params = node.params;
            let body = node.body;
            if(!t.isBlockStatement(body)){
                let returnStatement = t.returnStatement(body);
                body = t.blockStatement([returnStatement]);
            }
            let funcs = t.functionExpression(null, params, body, false, false);
            path.replaceWith(funcs);
        }
    }
}
let r = babel.transform(code, {
    plugins:[
        AllowPlugins
    ]
})

console.log(r.code);


2. класс

let code = `
class Jomsou{
    constructor(name){
        this.name = name;
    }
    getName(){
        return this.name;
    }
}
`

а) Реализовать преобразование конструктора

const babel = require('babel-core');//babel核心解析库
const t = require('babel-types');//babel类型转化库

/**
 * function Jomsou(name){
 *  this.name = name;
 * }
 * Jomsou.prototype.getName = function(){
 *  return this.name;
 * }
 */
let code = `
class Jomsou{
    constructor(name){
        this.name = name;
    }
    getName(){
        return this.name;
    }
}
`
let ClassPlugin = {
    visitor: {
        ClassDeclaration(path){
            let {node} = path;
            let className = node.id.name;
            className = t.identifier(className);
            //console.log(className);
            let funs = t.functionDeclaration(className, [], t.blockStatement([]), false, false);
            path.replaceWith(funs);
        }
    }
}
let d = babel.transform(code, {
    plugins: [
        ClassPlugin
    ]
})
console.log(d.code);

б) Преобразование функции метода класса реализации в метод-прототип

const babel = require('babel-core');//babel核心解析库
const t = require('babel-types');//babel类型转化库

/**
 * function Jomsou(name){
 *  
 * }
 */
let code = `
class Jomsou{
    constructor(name){
        this.name = name;
    }
    getName(){
        return this.name;
    }
}
`
let ClassPlugin = {
    visitor: {
        ClassDeclaration(path){
            let {node} = path;
            let className = node.id.name;
            className = t.identifier(className);
            let classList = node.body.body;
            //console.log(className);
            let funs = t.functionDeclaration(className, [], t.blockStatement([]), false, false);
            
            let es5func = [];

            classList.forEach((item, index)=>{
                let body = classList[index].body;
               
                if(item.kind==='constructor')
                {
                    let params = item.params.length?item.params.map(item=>item.name):[];
                    params = t.identifier(params);
                    funs = t.functionDeclaration(className, [params], body, false, false);
                    path.replaceWith(funs);
                }else {
                    let protoObj = t.memberExpression(className, t.identifier('prototype'));
                    let left = t.memberExpression(protoObj, t.identifier(item.key.name));
                    let right = t.functionExpression(null, [], body, false, false);

                    let assign = t.assignmentExpression('=', left, right);
                    es5func.push(assign);
                }
            })
            if(es5func.length==0)
            {
                path.replaceWith(funs);
            }
            else {
                es5func.push(funs);
                path.replaceWithMultiple(es5func);
            }
        }
    }
}
let d = babel.transform(code, {
    plugins: [
        ClassPlugin
    ]
})
console.log(d.code);

3. Реализовать загрузку модулей по требованию

eg:

010
006
007

//babel-plugin-固定的前缀,放在node_module里
//babel-plugin-czq-import
const babel = require('babel-core');//babel核心解析库
const t = require('babel-types');//babel类型转化库
let code = `import {Button, ALert} from 'antd'`;
let importPlugin = {
    visitor: {
        ImportDeclaration(path){
            let {node} = path;
            //console.log(node);
            let source = node.source.value;
            let specifiers =  node.specifiers;
            if(!t.isImportDefaultSpecifier(specifiers[0])){
                specifiers = specifiers.map(specifier=>{
                    return t.importDeclaration(
                        [t.importDefaultSpecifier(specifier.local)],
                        t.stringLiteral(`${source}/lib/${specifier.local.name.toLowerCase()}`)
                    )
                });
                path.replaceWithMultiple(specifiers);
            }
        }
    }
}
let r = babel.transform(code, {
    plugins: [
        importPlugin
    ]
})

module.exports = importPlugin;

финальный тест

Установите зависимости:

npm antd babel-preset-env babel-preset-react react react-dom webpack webpack-cli --save-dev

Тестовый код:

//test.js
import React from 'react';
import ReactDOM from 'react-dom';

import {Button} from 'antd';

тестовое задание:

npx webpack

Сравнение эффекта до и после использования babel-plugin-czq-import:

вперед:

008

назад:

009

оригинал:От компиляции и парсинга AST до написания плагинов для Babel

адрес проектаbabelPlugin

Справочный адрес:

официальный сайт эспримы

документация Babel на github