background

Recently, a leader wanted us to build a component library, and I wanted to know which of the tripartite component libraries currently used in the project were most frequently used. I wanted to consult my friend, but my friend was too busy, so I had to do it by myself. I wondered if I could implement my idea through Webpack

The effect

We’re using @material- UI, and here’s the component usage

implementation

We know that the 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 be counted and it will not be accurate, so we can do this in an AST way, and there are a lot of big guys who have talked about AST concepts so I won’t bother

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 statistics function is done, of course there may be other better way I just offer the idea, welcome to discuss

The last

What’s the point of doing this, for example, is that when our own component library goes live, we can count the number of component references, and we can count them on a time scale like a week. We can analyze the optimization direction of the next version of our component library through data, and we can also do KPI reporting. After all, there is data support