node-tutorial icon indicating copy to clipboard operation
node-tutorial copied to clipboard

AST

Open Wscats opened this issue 6 years ago • 0 comments

调试网站

AST测试网站 babel插件手册

babel

const template = require('@babel/template').default;
const generate = require("@babel/generator").default;

const ast = template.ast(`
// JS
var a = 'abcd'
`);
ast.kind = 'let';
ast.declarations[0].init.value = 1234;
console.log(ast.declarations[0].init);

console.log(generate(ast).code);

这段代码通过AST把var a = 'abcd'转化为let b = 1234

babel-plugins

babel插件开发,plugin字段配合visitor筛选代码实现转化,转换的时候,是插件开始起作用的时候,babel给我们提供了一个visitor的规范,让我们进入到这个流程中,我们可以通过visitor来定义我们的访问逻辑

visitor支持很多类型,比如VariableDeclarationFunctionDeclaration ImportDeclaration 等,具体可以从AST测试网站展开分析

const {
    transform
} = require("@babel/core");
module.exports = async (option, options) => {
    let {
        script,
        allScript,
        isExistScript,
        scriptLang,
        template,
        templateLang,
        templateComponentName,
        style,
        styleLang,
        isExistStyle
    } = option
    const result = await new Promise((resolve, reject) => {
        const defaultOption = {
            plugins: [
                require("@babel/plugin-proposal-class-properties"),
                {
                    visitor: {
                        ArrayExpression(path) {
                            console.log(path.node)
                        }
                    }
                }
            ],
            presets: [
                [
                    require("@babel/preset-react"),
                    {
                        "pragma": "h",
                    }
                ]
            ]
        }
        // comibine option
        const finalOptions = Object.assign({}, defaultOption, {
            ...options
        });
        transform(allScript, {
            ...finalOptions
        }, (err, result) => {
            if (err) {
                reject(err)
            } else {
                // console.log(result)
                resolve(result)
            }
        });
    })
    return result
}

访问者模式

可以在方法名用|来匹配多种不同的type,使用相同的处理函数

visitor: {
    "ClassExpression|VariableDeclaration"(path) {
        console.log(path.node.body)
    }
}

遍历

可以手动查找就不要遍历

path.node.params.forEach(function() {
      // ...
});

获取子节点的path

BinaryExpression(path) {
    path.get('left');
},
Program(path) {
    path.get('body.0');
}

获取父节点的path

findParent

获取对应的子节点的

path.find((path) => path.isObjectExpression());
path.find((path) => path.isReturnStatement()); //获取return的地方

babel-types

配合babel-types工具进行代码替换增加等

const t = require("babel-types");

用多节点替换单节点

ReturnStatement(path) {
  path.replaceWithMultiple([
    t.expressionStatement(t.stringLiteral("Is this the real life?")),
    t.expressionStatement(t.stringLiteral("Is this just fantasy?")),
    t.expressionStatement(t.stringLiteral("(Enjoy singing the rest of the song in your head)")),
  ]);
}

还可以替换代码,不过经过测试,它有局限性,官方文档也不建议使用

FunctionDeclaration(path) {
  path.replaceWithSourceString(`function add(a, b) {
    return a + b;
  }`);
}

技巧

利用visitor访问者模式获取函数片段,然后通过path.get(类型)一层一层定位到对应的子路径然后执行path.xxx改造代码片段,获取子属性可以先在AST测试网站先查找结果

visitor: {
    "ClassExpression"(path, { opts }) {
        // 筛选class myAbcAbc extends WeElement类
        if (path.node.superClass.name === 'WeElement' && path.get("body.body")) {
            console.log('---------------')
            path.get("body.body").forEach((ClassMethod) => {
                // 筛选render() {}函数
                if (ClassMethod.node.key.name === 'render') {
                    console.log(ClassMethod.type)
                    ClassMethod.remove()
                }
            })
        }
    }
}

loader里面获取文件的路径:

const filename = this.resourcePath;

踩过的坑

类里面的方法要使用 ClassMethod 匹配,不能使用 FunctionExpression 匹配。

参考网站

Wscats avatar Jun 11 '19 07:06 Wscats