50 строк кода для реализации статистики использования компонентов

Webpack

задний план

Недавно руководитель хотел, чтобы мы создали библиотеку компонентов, и тогда я хотел узнать, какие компоненты трехсторонней библиотеки компонентов, используемые в настоящее время в проекте, используются наиболее часто. Изначально хотел посоветоваться с другом, но мой друг был слишком занят, поэтому пришлось делать самому. Интересно, смогу ли я реализовать свою идею через webpack

Эффект

мы используем@material-ui, ниже показано использование компонента

выполнить

Мы знаем, что источником загрузчика является статическая строка файла, как показано ниже.

Самое быстрое решение - использовать обычный метод для строковой статистики, но будет проблема, что если в части есть комментарии, то они тоже будут считаться неточно, поэтому мы можем добиться этого с помощью AST. кто говорил о концепции, так что я не буду повторяться.

Анализ АСТ

Моя сторона анализируется @babel/parser, давайте сначала посмотрим на состав следующего кода на сайте

import { Box } from '@material-ui/core';
import Autocomplete from '@material-ui/lab/Autocomplete';

Мы видим, что путь program => body , а затем объявленный типtype: ImportDeclaration, продолжайте видеть следующую композицию

"source": {
          "type": "StringLiteral",
          "value": "@material-ui/core"
  },
 // 第二段
  "source": {
           type": "StringLiteral",
          "value": "@material-ui/lab/Autocomplete"
 },

Мы отправляем значение в этом поле с желаемым именем пакета, поэтому первый фрагмент кода

const ast = parser.parse(source, {
            sourceType: 'module',
            plugins: ['jsx'],
 });
const getImport = 'ImportDeclaration';
const getMaterialImport = packageName || '@material-ui';
const importAst = ast.program.body.filter(
    // type 节点类型,这里我们去过滤 import 声明类型 同时去过滤
    (i) => i.type === getImport && i.source.value.includes(getMaterialImport),
);

После получения соответствующего массива ast следующим шагом будет получение имени компонента, Наблюдая, мы обнаружили, чтоspecifiersИдентификатор Это поле содержит два поля, содержащие имя компонента:imported,local

  • импортированная переменная, представляющая полученную из модуля экспорта
  • LOCAL указывает переменную текущего модуля после импорта

Здесь я беру локальный, потому что следующий метод не появляетсяimportedэто поле

import Autocomplete from '@material-ui/lab/Autocomplete';

В это время также получается имя пакета, и можно просто перейти прямо к теме и вставить полный код.

demo

const parser = require('@babel/parser');
const loaderUtils = require('loader-utils');
const total = {
    len: 0,
    components: {},
};
// 对象排序
const sortable = (obj) => Object.fromEntries(Object.entries(obj).sort(([, a], [, b]) => b - a));
module.exports = function(source) {
    console.log(source, '--');
    const options = loaderUtils.getOptions(this);
    const { packageName = '' } = options;
    const callback = this.async();
    if (!packageName) return callback(null, source);
    try {
        // 解析成 ast
        const ast = parser.parse(source, {
            sourceType: 'module',
            plugins: ['jsx'],
        });
        if (ast) {
            setTimeout(() => {
                const getImport = 'ImportDeclaration';
                const getMaterialImport = packageName;
                const importAst = ast.program.body.filter(
                    // type 节点类型,这里我们去过滤 import 声明类型 同时去过滤
                    (i) => i.type === getImport && i.source.value.includes(getMaterialImport),
                );
                total.len = total.len + importAst.length;
                for (let i of importAst) {
                    const { specifiers = [] } = i;
                    for (let s of specifiers) {
                        if (s.local) {
                            const { name } = s.local;
                            total.components[name] = total.components[name] ? total.components[name] + 1 : 1;
                        }
                    }
                }
                total.components = sortable(total.components);
                console.log(total, 'total');
                callback(null, source);
            }, 0);
        } else callback(null, source);
    } catch (error) {
        callback(null, source);
    }
};

вызов загрузчика

 {
    test: /\.(jsx|)$/,
    exclude: /node_modules/,
    include: [appConfig.eslintEntry],
    use: [
        {
            loader: path.resolve(__dirname, './loader/total.js'),
            options: {
                packageName: '@material-ui',
            },
        },
    ],
},

плагин путь

представлять

[
vincePlugin,
  {
    libraryName: '@material-ui',
  },
],

код плагина

const chalk = require('chalk');
const total = {
    len: 0,
    components: {},
};
let cache = -1;
let task = setInterval(() => {
    console.log(chalk.green('~~~~统计中~~~~'));
    if (total.len !== cache) {
        cache = total.len;
    } else {
        clearInterval(task);
        console.log(total);
    }
}, 1000);
const sortable = (obj) => Object.fromEntries(Object.entries(obj).sort(([, a], [, b]) => b - a));
module.exports = function(babel) {
    var t = babel.types;
    return {
        visitor: {
            ImportDeclaration(path, source) {
                const {
                    opts: { libraryName = '@material-ui' },
                } = source;
                if (path.node.source.value.includes(libraryName)) {
                    const { specifiers = [] } = path.node;
                    total.len = total.len + 1;
                    for (let s of specifiers) {
                        if (s.local) {
                            const { name } = s.local;
                            total.components[name] = total.components[name] ? total.components[name] + 1 : 1;
                        }
                    }
                    total.components = sortable(total.components);
                }
            },
        },
    };
};

Простая статистическая функция выполнена, конечно, могут быть и другие лучшие способы, я просто предлагаю эту идею, все желающие могут обсудить

наконец

В чем смысл этого?Например, после того, как наша собственная библиотека компонентов будет подключена к сети, мы можем подсчитать количество ссылок на компоненты и взять в качестве измерения определенное время, например неделю. Анализируйте направление оптимизации следующей версии нашей библиотеки компонентов через данные, а также можете делать отчеты по KPI.Ведь есть поддержка данных