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

  • 分析web应用内引用依赖的占比已关闭评论
  • 104 次浏览
  • A+
所属分类:Web前端
摘要

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


背景

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

主要思路

目前找到的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/[email protected]/node_modules/react/index.js"       },     ...       ]   },   ... ] 

后续

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