[前端自动化]手摸手教你实现一个babel插件并统计Antd组件的引用情况

345 阅读2分钟

为什么要实现一个babel插件:

  1. 有时间,有精力,写写代码,滑滑水,大家一起快乐地赚ZB主义的💰
  2. 面试官:自己有实现过babel的相关插件吗?我:额(⊙o⊙)…

需要具备哪些前置条件:

  1. babel的基本概念及使用方法
  2. 了解AST(抽象语法树)的基本结构
  3. 再具备一些能正确copy的能力

如何实现一个babel插件:

Talk is cheap, show me the code(直接上源码)

  1. 在项目根目录下新建comp-analysis-plugin.js
  2. 新增如下代码:
const chalk = require('chalk');
const fs = require('fs')
const analysisInfo = {
    packageName: '',
    sum: 0,
    components: {},
}

// 按照引用的次数,对components进行排序(由大到小)
const sortObj = obj => Object.fromEntries(Object.entries(obj).sort(([ ,a], [ ,b]) => b - a))

module.exports = function({ types: t }) {
    return {
        visitor: {
            ImportDeclaration: { // 通过import方式引入的
                enter(path, source) {
                    // opts的相关配置在 .babelrc 中设置
                    const { opts: { packageName="" } } = source;
                    
                    // 相关对象可以直接分析AST语法树的结构
                    if (path.node.source.value === packageName) {
                        console.log(chalk.green(`~~~~正在统计${packageName}的引用情况~~~~`));
                        analysisInfo.packageName = packageName
                        analysisInfo.sum += 1
                        
                        // 相关组件可以在 specifiers 中找到
                        const { specifiers = [] } = path.node
                        for(let i of specifiers) {
                            // 此处需要取 local下 的相关字段
                            if(i.local) {
                                const { name } = i.local
                                analysisInfo.components[name] = analysisInfo.components[name] ? analysisInfo.components[name] + 1 : 1
                            }
                        }
                        // 对components进行排序
                        analysisInfo.components = sortObj(analysisInfo.components)
                    }
                },
                exit(path, source) {
                    const { opts: { packageName="" } } = source;
                    if (path.node.source.value === packageName) {
                        let str = `包名称: ${packageName}\n组件引用总次数: ${analysisInfo.sum}\n组件引用详情\n`

                        Object.keys(analysisInfo.components).map(function(name) {
                            str += `${name}: ${analysisInfo.components[name]}\n`
                        })

                        // 将 analysisInfo 相关信息写入 comp-analysis-plugin.md
                        const fileName = `comp-analysis-plugin.md`
                        
                        // 异步写入相关文件
                        fs.writeFile(fileName, str, (err) => {
                            if(err) throw err;
                        })
                    }
                }
            }
        },
    }
}

如何在babel中引用上述插件

  1. 找到项目中的 .babelrc 文件(一般放在项目根目录下)
  2. 找到 plugins 对象,新增如下引用

此处存在一个冲突:如果你们的项目使用了ts-loader,建议用@babel/preset-typescript去代替它,因为该插件是运行在babel-loader中的,使用babel-loader和ts-loader去转化.ts、.tsx等文件,会存在问题

{
    "presets": [
        ...
    ],
    "plugins": [
        ...
        ["./comp-analysis-plugin", { "packageName": "antd" }] +++// 此处的引用路径根据comp-analysis-plugin.js的实际路径确定,我直接放在根目录下了;需要分析的packageName也需要根据实际需要配置
    ]
}
  1. 执行项目的打包命令
npm run build
或
yarn build
  1. 在项目根目录下,会新建 comp-analysis-plugin.md 文件,类似如下内容:
包名称: antd
组件引用总次数: 98
组件引用详情:
notification: 58
Row: 55
Button: 49
Modal: 49
Col: 48
Select: 44
Input: 42
Spin: 37
Icon: 27
Form: 27
Popover: 22
Radio: 21
Table: 18
Tag: 16
DatePicker: 14
InputNumber: 14
Tabs: 13
Checkbox: 9
Switch: 9
Pagination: 6
Empty: 6
Dropdown: 6
Menu: 6
TimePicker: 5
Alert: 4
Cascader: 4
Upload: 3
Divider: 3
Tooltip: 2
Layout: 2
Badge: 2
Popconfirm: 2
Card: 2
ConfigProvider: 1
Progress: 1

插件实现思路和相关源码参考自网络,在此基础上进行了二次完善,上述源码已亲测,后续会同步至Github。