AnnVoV / blog

24 stars 2 forks source link

写一个对象判空的babel插件 #27

Open AnnVoV opened 5 years ago

AnnVoV commented 5 years ago

前言

上一篇写了通过写babel插件来入门ast, 在组内分享的时候,有同学说是不是可以写一个判空的插件呢? 把a.b => a && a.b呢?我觉得是的,这完全可以做呀!然后就尝试着在分享后写了一个。

在线示例:

https://astexplorer.net/#/gist/a54996b88a3fd57b65dbd9f2a14b4c91/fd5dbe0ea9ca616226980861f9d81e0984568753

转换前:

var change = babelCheckEmpty(a.b.c.d);
var notChange = d && d.e;

转换后

var change = a && (a.b && a.b.c && a.b.c.d);
var notChange = d && d.e;

我只会去转换带有babelCheckEmpty 包裹的方法,这样避免影响其他方法的调用, 代码我贴在下面

module.exports = function (babel) {
    const {types: t} = babel;
    let isEnter = false;
    let objectName = '';
    let stack = [];

    function getTemplateAst(tpl, opts = {}) {
        let ast = babel.template(tpl, opts)({});
        if (Array.isArray(ast)) {
            return ast;
        } else {
            return [ast];
        }
    }

    function generateExpressionStack(stack = []) {
        if (stack.length === 0) {
            return [];
        }
        let result = [];
        stack.reduce((sum, current) => {
            const nowStr = sum.concat('.').concat(current);
            result.push(nowStr);
            return nowStr;
        });
        return result;
    }

    return {
        name: "ast-transform", // not required
        visitor: {
            CallExpression: {
                enter(path) {
                    if (path.node.callee.name !== 'babelCheckEmpty') {
                        return;
                    }
                    isEnter = true;
                },
                exit(path) {
                    if (path.node.callee.name !== 'babelCheckEmpty') {
                        return;
                    }
                    isEnter = false;
                    const resultArr = generateExpressionStack(stack);
                    const arr = getTemplateAst(resultArr.join('&&')) || [];
                    if (arr.length === 0 || stack.length === 0) {
                        return;
                    }
                    path.replaceWith(
                        t.logicalExpression('&&', t.identifier(stack[0]), arr[0].expression),
                    );
                    stack = [];
                },
            },
            MemberExpression(path) {
                if (!isEnter || !path.node) return;
                objectName = path.node.object.name;
                if (t.isIdentifier(path.node.property)) {
                    stack.unshift(path.node.property.name);
                }
                if (objectName) {
                    stack.unshift(objectName);
                }
            },
        },
    };
};

由于直接用逻辑运算自己去构造一个 a.b && a.b.c && a.b.c.d 的ast过于复杂,其实是我不知道到底要怎么去写,所以我就使用了一个上一篇内容没有讲到的一个方法,直接利用babel.template 的API 根据传入的内容直接生成ast

let ast = babel.template(tpl, opts)({});

放入工程里的截图

参考资料: 1.Babel 从入门到插件开发 https://juejin.im/entry/5912ba62a22b9d005819cff7 2.ast 入门(从写babel插件来深入了解ast https://github.com/AnnVoV/blog/issues/26