分析web应用内引用依赖的占比

yizhihongxing

背景

针对目前团队自己开发的组件库,对当前系统内引用组件库占比进行统计分析,以实现对当前进度的总结以及后续的覆盖度目标制定。

主要思路

目前找到的webpack分析插件,基本都是针对打包之后的分析打包之后的chunk进行分析,但是我希望的是分析每个页面中的import数,对比一下在所有页面中的import数中有多少是使用了组件库的。所以就在网上看了一些相关资料以及webpackapi文档。主要是利用webpackimportCallimportimportSpecifier三个钩子来实现,它们的作用直接跟着代码看一下。

完整代码实现

import fs from 'fs';
import path from 'path';
import resolve from 'enhanced-resolve';

let myResolve;

/**
 * 通过source获取真实文件路径
 * @param parser
 * @param source
 */
function getResource(parser, source) {
  if (!myResolve) {
    myResolve = resolve.create.sync(parser.state.options.resolve);
  }
  let result = '';
  try {
    result = myResolve(parser.state.current.context, source);
  } catch (err) {
    console.log(err);
  } finally {
    return result;
  }
}

class WebpackImportAnalysisPlugin {
  constructor(props) {
    this.pluginName = 'WebpackCodeDependenciesAnalysisPlugin';
    //  文件数组
    this.files = [];
    //  当前编译的文件
    this.currentFile = null;
    this.output = props.output;
  }

  apply(compiler) {
    compiler.hooks.compilation.tap(this.pluginName, (compilation, { normalModuleFactory }) => {
      const collectFile = parser => {
        const { rawRequest, resource } = parser.state.current;
        if (resource !== this.currentFile) {
          this.currentFile = resource;
          this.files.push({
            name: rawRequest,
            resource,
            children: []
          });
        }
      };
      const handler = parser => {
        // 用来捕获import(xxx)
        parser.hooks.importCall.tap(this.pluginName, expr => {
          collectFile(parser);
          let ast = {};
          const isWebpack5 = 'webpack' in compiler;
          // webpack@5 has webpack property, webpack@4 don't have the property
          if (isWebpack5) {
            // webpack@5
            ast = expr.source;
          } else {
            //webpack@4
            const { arguments: arg } = expr;
            ast = arg[0];
          }
          const { type, value } = ast;
          if (type === 'Literal') {
            const resource = getResource(parser, value);
            this.files[this.files.length - 1].children.push({
              name: value,
              resource,
              importStr: `import ('${value}')`
            });
          }
        });
        // 用来捕获 import './xxx.xx';
        parser.hooks.import.tap(this.pluginName, (statement, source) => {
          // 由于statement.specifiers.length大于0的时候同时会被importSpecifier钩子捕获,所以需要在这个地方拦截一下,这个地方只处理单独的引入。
          if (statement.specifiers.length > 0) {
            return;
          }
          collectFile(parser);
          this.files[this.files.length - 1].children.push({
            name: source,
            resource: getResource(parser, source),
            importStr: `import '${source}'`
          });
        });
        // 用来捕获 import xx from './xxx.xx';
        parser.hooks.importSpecifier.tap(
          this.pluginName,
          (statement, source, exportName, identifierName) => {
            collectFile(parser);
            let importStr = '';
            if (exportName === 'default') {
              importStr = `import ${identifierName} from '${source}'`;
            } else {
              if (exportName === identifierName) {
                importStr = `import { ${identifierName} } from '${source}'`;
              } else {
                importStr = `import { ${exportName}: ${identifierName} } from '${source}'`;
              }
            }
            this.files[this.files.length - 1].children.push({
              name: source,
              exportName,
              identifierName,
              importStr,
              resource: getResource(parser, source)
            });
          }
        );
      };

      normalModuleFactory.hooks.parser.for('javascript/auto').tap(this.pluginName, handler);
    });

    compiler.hooks.make.tap(this.pluginName, compilation => {
      compilation.hooks.finishModules.tap(this.pluginName, modules => {
        // 过滤掉深度遍历的node_modules中的文件,只分析业务代码中的文件
        const needFiles = this.files.filter(
          item => !item.resource.includes('node_modules') && !item.name.includes('node_modules')
        );
        fs.writeFile(this.output ?? path.resolve(__dirname, 'output.json'), JSOn.stringify(needFiles, null, 4), err => {
          if (!err) {
            console.log(`${path.resolve(__dirname, 'output.json')}写入完成`);
          }
        });
      });
    });
  }
}

export default WebpackImportAnalysisPlugin;
// 以文件为基准,扁平化输出所有的import
[
  {
    "name": "./src/routes",
    "resource": "/src/routes.tsx",
    "children": [
      {
        "name":"react",
        "exportName":"lazy",
        "identifierName":"lazy",
        "importStr":"import { lazy } from 'react'",
        "resource":"/node_modules/.pnpm/react@17.0.2/node_modules/react/index.js"
      },
    ...  
    ]
  },
  ...
]

后续

上面拿到的数据是扁平化的数据,如果针对需要去分析整体的树状结构,可以直接将扁平化数据处理一下,定义一个主入口去寻找它的子级,这样可以自己去生成一颗树状的import关系图。

原文链接:https://www.cnblogs.com/aloneMing/p/17316100.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:分析web应用内引用依赖的占比 - Python技术站

(0)
上一篇 2023年4月17日
下一篇 2023年4月17日

相关文章

  • JavaScript的事件机制详解

    JavaScript的事件机制详解 事件机制是 JavaScript 重要的一部分,它使得 Web 应用得以用户交互和响应。在本文中,我将详细讲解 JavaScript 的事件机制,包括事件模型、事件流以及事件捕获和冒泡。 事件模型 JavaScript 事件模型是一种基于事件触发的编程模型。当用户操作网页中的元素(如按钮、文本输入框等)时,会触发事件,Ja…

    JavaScript 2023年5月28日
    00
  • JS获取屏幕高度的简单实现代码

    当我们需要获取浏览器窗口可视区域的高度时,可以使用JavaScript代码来实现。下面是获取屏幕高度的简单实现代码攻略: 1.通过window对象获取屏幕高度 我们可以通过window对象来获取浏览器窗口的高度。这里有一个简单的例子: var height = window.innerHeight; 其中,window.innerHeight属性用于获取浏览…

    JavaScript 2023年6月11日
    00
  • JavaScript的学习入门整理篇

    让我来详细讲解一下 JavaScript 的学习入门整理篇。 前言 JavaScript 可以说是前端开发的必备技能,不仅可以用于 DOM 操作,还可以实现复杂的交互效果、表单验证和数据存储等。对于初学者来说,学习 JavaScript 需要掌握一定的基础知识,包括语法、数据类型、函数、对象等,才能更好地理解和运用它。 基础语法 变量和数据类型 JavaSc…

    JavaScript 2023年5月18日
    00
  • JavaScript通过字符串调用函数的实现方法

    JavaScript可以通过字符串调用函数的方法称为函数调用方法,也就是将字符串转化成函数名称并直接调用对应的函数。下面是一些实现方法的示例说明: 1. 使用eval()函数实现字符串调用函数 使用eval()函数可以将字符串解析成JS代码并执行,从而实现字符串调用函数的方法。 // 定义需要调用的函数 function greeting(message) …

    JavaScript 2023年5月28日
    00
  • javascript基础的动画教程,直观易懂

    Javascript基础动画教程 在本文中,我们将详细介绍Javascript动画的基础知识。这些知识将帮助你创建直观易懂的动画效果,使你的网页更加生动活泼。 动画基础知识 在Javascript动画中,我们通常使用以下基础知识: 1. 定时器 定时器在Javascript动画中起到很重要的作用。使用定时器,我们可以按照预定的时间间隔执行特定的代码。一般来说…

    JavaScript 2023年6月10日
    00
  • HTML页面嵌入视频与JS控制切换视频示例详解

    HTML页面嵌入视频与JS控制切换视频示例详解 HTML页面中嵌入视频是网页制作一个比较基础的功能,在现代网站中,视频的应用越来越广泛,这里我们将介绍如何嵌入视频,并用JS控制切换视频的方法。 嵌入视频 HTML5中嵌入视频,可以使用video标签实现,示例代码如下: <video src="video.mp4" controls&…

    JavaScript 2023年6月11日
    00
  • js无痛刷新Token的实现

    当我们使用前后端分离的架构时,前端需要向后端服务器发送API请求获取数据,而前端需要在每次请求时,将后端返回的Token设置在请求的Header中。Token是用来验证用户身份的,它的有效期是有限的,过期后需要重新向服务器获取。在这种情况下,我们需要处理Token过期的问题。下面,我将提供一个JS无痛刷新Token的实现的完整攻略: Step1:在前端存储T…

    JavaScript 2023年5月19日
    00
  • JS中prototype的用法实例分析

    接下来我将为你详细介绍“JS中prototype的用法实例分析”的完整攻略。 什么是Prototype JS中的每一个对象都有一个Prototype链,它指向了另一个对象,这个对象叫做“原型”,这样就可以实现某些属性和方法的继承。 当我们需要给一个JS对象添加属性或方法时,可以通过prototype来实现。在使用prototype属性时,我们需要明确两点: …

    JavaScript 2023年6月11日
    00
合作推广
合作推广
分享本页
返回顶部