Author: Platform front End team — Ocean
preface
Want to know the data for the tripartite component library used in your project? Come on, this article shows you how to count component usage in a project.
Function: Make statistics of the data used by components in the project, and make statistics of high-frequency components (convenient for later targeted iteration)
Webpack Loader (AST)
The final result
For example, after a project starts using Ant Design, component usage statistics can be obtained:
How to implement
We know that loader source is a static string of files as shown below:
The fastest way to do this is to use string statistics in a regular way, but the problem with this is that if there is a comment section, it will also be counted and not accurate, so I thought of an AST(abstract syntax tree) way to implement this, there are a lot of big guys about AST concept I will not be verbose.
Analysis the AST
My side is through @babel/parser to analyze, let’s first look at the following code on the website.
import { Box } from '@material-ui/core';
import Autocomplete from '@material-ui/lab/Autocomplete';
Copy the code
We can see the path program => body, and then declare the type type: ImportDeclaration
"source": {
"type": "StringLiteral"."value": "@material-ui/core"
},
/ / the second paragraph
"source": {
type":"StringLiteral","value":"@material-ui/lab/Autocomplete"},Copy the code
We’ll post that the value in this field has the package name that we want so the first piece of code is going to be
const ast = parser.parse(source, {
sourceType: 'module'.plugins: ['jsx']});const getImport = 'ImportDeclaration';
const getMaterialImport = packageName || '@material-ui';
const importAst = ast.program.body.filter(
// type Specifies the node type
(i) = > i.type === getImport && i.source.value.includes(getMaterialImport),
);
Copy the code
The next step in getting the relevant AST array is to get the component name. By looking at the speciFIERS identifier, we found that there are two fields that contain the component name: Imported and local.
- Imported represents variables exported from the exported module
- Local indicates the variables of the current module after import
And I’m going to call it local, because it doesn’t have the imported field.
import Autocomplete from '@material-ui/lab/Autocomplete';
Copy the code
At this time, the name of the package also got the following simple to go straight to the theme of the complete code.
demo
const parser = require('@babel/parser');
const loaderUtils = require('loader-utils');
const total = {
len: 0.components: {}};// Sort objects
const sortable = (obj) = > Object.fromEntries(Object.entries(obj).sort(([, a], [, b]) = > b - a));
module.exports = function(source) {
console.log(source, The '-');
const options = loaderUtils.getOptions(this);
const { packageName = ' ' } = options;
const callback = this.async();
if(! packageName)return callback(null, source);
try {
// Parse into 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 Specifies the node type
(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); }};Copy the code
Call the loader
{
test: /\.(jsx|)$/,
exclude: /node_modules/,
include: [appConfig.eslintEntry],
use: [{loader: path.resolve(__dirname, './loader/total.js'),
options: {
packageName: '@material-ui',},},],},Copy the code
The way the plugin
The introduction of
[
vincePlugin,
{
libraryName: '@material-ui'],},Copy the code
The Plugin code
const chalk = require('chalk');
const total = {
len: 0.components: {}};let cache = -1;
let task = setInterval(() = > {
console.log(chalk.green('~~~~ statistics ~~~~'));
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); ,}}}}; };Copy the code
A simple statistical function is done, of course there may be other better ways to do it but I’m just offering this idea, and I welcome you to come and talk about it.
The last
With this plug-in, we can count the data of our component library after it goes live, and in a certain time dimension. Use data to tell you how often the current component is being used in order to determine future optimization directions.