翼度科技»论坛 编程开发 JavaScript 查看内容

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

2

主题

2

帖子

6

积分

新手上路

Rank: 1

积分
6
背景
  1. 针对目前团队自己开发的组件库,对当前系统内引用组件库占比进行统计分析,以实现对当前进度的总结以及后续的覆盖度目标制定。
复制代码
主要思路

目前找到的webpack分析插件,基本都是针对打包之后的分析打包之后的chunk进行分析,但是我希望的是分析每个页面中的import数,对比一下在所有页面中的import数中有多少是使用了组件库的。所以就在网上看了一些相关资料以及webpack的api文档。主要是利用webpack的importCall、import、importSpecifier三个钩子来实现,它们的作用直接跟着代码看一下。
完整代码实现
  1. import fs from 'fs';
  2. import path from 'path';
  3. import resolve from 'enhanced-resolve';
  4. let myResolve;
  5. /**
  6. * 通过source获取真实文件路径
  7. * @param parser
  8. * @param source
  9. */
  10. function getResource(parser, source) {
  11.   if (!myResolve) {
  12.     myResolve = resolve.create.sync(parser.state.options.resolve);
  13.   }
  14.   let result = '';
  15.   try {
  16.     result = myResolve(parser.state.current.context, source);
  17.   } catch (err) {
  18.     console.log(err);
  19.   } finally {
  20.     return result;
  21.   }
  22. }
  23. class WebpackImportAnalysisPlugin {
  24.   constructor(props) {
  25.     this.pluginName = 'WebpackCodeDependenciesAnalysisPlugin';
  26.     //  文件数组
  27.     this.files = [];
  28.     //  当前编译的文件
  29.     this.currentFile = null;
  30.     this.output = props.output;
  31.   }
  32.   apply(compiler) {
  33.     compiler.hooks.compilation.tap(this.pluginName, (compilation, { normalModuleFactory }) => {
  34.       const collectFile = parser => {
  35.         const { rawRequest, resource } = parser.state.current;
  36.         if (resource !== this.currentFile) {
  37.           this.currentFile = resource;
  38.           this.files.push({
  39.             name: rawRequest,
  40.             resource,
  41.             children: []
  42.           });
  43.         }
  44.       };
  45.       const handler = parser => {
  46.         // 用来捕获import(xxx)
  47.         parser.hooks.importCall.tap(this.pluginName, expr => {
  48.           collectFile(parser);
  49.           let ast = {};
  50.           const isWebpack5 = 'webpack' in compiler;
  51.           // webpack@5 has webpack property, webpack@4 don't have the property
  52.           if (isWebpack5) {
  53.             // webpack@5
  54.             ast = expr.source;
  55.           } else {
  56.             //webpack@4
  57.             const { arguments: arg } = expr;
  58.             ast = arg[0];
  59.           }
  60.           const { type, value } = ast;
  61.           if (type === 'Literal') {
  62.             const resource = getResource(parser, value);
  63.             this.files[this.files.length - 1].children.push({
  64.               name: value,
  65.               resource,
  66.               importStr: `import ('${value}')`
  67.             });
  68.           }
  69.         });
  70.         // 用来捕获 import './xxx.xx';
  71.         parser.hooks.import.tap(this.pluginName, (statement, source) => {
  72.           // 由于statement.specifiers.length大于0的时候同时会被importSpecifier钩子捕获,所以需要在这个地方拦截一下,这个地方只处理单独的引入。
  73.           if (statement.specifiers.length > 0) {
  74.             return;
  75.           }
  76.           collectFile(parser);
  77.           this.files[this.files.length - 1].children.push({
  78.             name: source,
  79.             resource: getResource(parser, source),
  80.             importStr: `import '${source}'`
  81.           });
  82.         });
  83.         // 用来捕获 import xx from './xxx.xx';
  84.         parser.hooks.importSpecifier.tap(
  85.           this.pluginName,
  86.           (statement, source, exportName, identifierName) => {
  87.             collectFile(parser);
  88.             let importStr = '';
  89.             if (exportName === 'default') {
  90.               importStr = `import ${identifierName} from '${source}'`;
  91.             } else {
  92.               if (exportName === identifierName) {
  93.                 importStr = `import { ${identifierName} } from '${source}'`;
  94.               } else {
  95.                 importStr = `import { ${exportName}: ${identifierName} } from '${source}'`;
  96.               }
  97.             }
  98.             this.files[this.files.length - 1].children.push({
  99.               name: source,
  100.               exportName,
  101.               identifierName,
  102.               importStr,
  103.               resource: getResource(parser, source)
  104.             });
  105.           }
  106.         );
  107.       };
  108.       normalModuleFactory.hooks.parser.for('javascript/auto').tap(this.pluginName, handler);
  109.     });
  110.     compiler.hooks.make.tap(this.pluginName, compilation => {
  111.       compilation.hooks.finishModules.tap(this.pluginName, modules => {
  112.         // 过滤掉深度遍历的node_modules中的文件,只分析业务代码中的文件
  113.         const needFiles = this.files.filter(
  114.           item => !item.resource.includes('node_modules') && !item.name.includes('node_modules')
  115.         );
  116.         fs.writeFile(this.output ?? path.resolve(__dirname, 'output.json'), JSOn.stringify(needFiles, null, 4), err => {
  117.           if (!err) {
  118.             console.log(`${path.resolve(__dirname, 'output.json')}写入完成`);
  119.           }
  120.         });
  121.       });
  122.     });
  123.   }
  124. }
  125. export default WebpackImportAnalysisPlugin;
复制代码
  1. // 以文件为基准,扁平化输出所有的import
  2. [
  3.   {
  4.     "name": "./src/routes",
  5.     "resource": "/src/routes.tsx",
  6.     "children": [
  7.       {
  8.         "name":"react",
  9.         "exportName":"lazy",
  10.         "identifierName":"lazy",
  11.         "importStr":"import { lazy } from 'react'",
  12.         "resource":"/node_modules/.pnpm/react@17.0.2/node_modules/react/index.js"
  13.       },
  14.     ...  
  15.     ]
  16.   },
  17.   ...
  18. ]
复制代码
后续

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

来源:https://www.cnblogs.com/aloneMing/p/17316100.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具