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.