задний план
Недавно руководитель хотел, чтобы мы создали библиотеку компонентов, и тогда я хотел узнать, какие компоненты трехсторонней библиотеки компонентов, используемые в настоящее время в проекте, используются наиболее часто. Изначально хотел посоветоваться с другом, но мой друг был слишком занят, поэтому пришлось делать самому. Интересно, смогу ли я реализовать свою идею через 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.Ведь есть поддержка данных