如何统计前端项目中npm包的使用率
最近接了个需求, 需要统计公司前端项目中, 自研 npm 包的普及度&包内函数使用量. 解决过程比较有意思, 这里分享下.
项目的基础思路比较简单, 大致如下图所示.
对于获取所有前端项目问题, 由于我司有一套自建的公共前端打包平台, 可以直接调用平台接口拉取项目源码.
所以剩下的难点只有一个: 如何解析 js 文件, 得到目标 npm 包内导出对象的使用次数
.
其实方法也很简单: babel 怎么做, 我们就怎么做.
用过 babel 的人都知道: babel 可以读取 ES6 代码, 先将 js 文件整体转化为抽象语法树, 然后遍历语法树, 调用插件对代码内容进行调整, 剔除/转换语法结构, 并最终输出为 ES5 代码. 而我们需要做的, 就是编写一个插件, 在 babel 遍历语法树时, 识别目标 npm 包, 统计从包中引出的变量使用情况. 流程如下.
然后剩下的就是体力活: babel 解析出的所有语法树节点类型都在babel-types
包中, 需要做的, 就是针对包中的每一种语法结构(导入/变量解构/重命名/函数调用/…)编写处理函数, 最后将所有结果输出为一个 json.
代码比较冗长, 全文可以翻看这个Github 项目, 这里只展示一下用于统计的数据解构
项目数据汇总: SummaryCollection
针对每个项目创建一个SummaryCollection
对象. 调用 add 方法登记每个文件的解析结果
函数签名 | 功能 | 备注 |
---|---|---|
constructor() | 初始化汇总类 | 汇总项目内所有文件的分析记录 |
add(target: UsedSummaryInFile) | 添加文件分析数据 | |
toJson(): TypeUiLibReport[] | 输出汇总结果 |
文件数据汇总: UsedSummaryInFile
针对单个 js 文件, 统计目标 npm 的使用记录
函数签名 | 功能 | 备注 |
---|---|---|
constructor(fileUri: string) | 初始化文件分析记录 | 记录文件fileUri 中的 npm 包使用数据 |
addLib(libName: string) | 发现目标 npm 后, 登记 npm 包名 | |
addLibAlias(libName: string, aliasName: string) | 登记目标 npm 包的别名 | |
addComponent(libName: string, componentName: string) | 登记目标 npm 包下组件 | |
addComponentAlias(libName: string, componentName: string, componentNameAlias: string) | 登记目标 npm 包下组件的别名 | |
incrComponentUseCount(libName: string, componentName: string) | npm 包下组件使用次数+1 | |
incrLibUseCount(libName: string) | npm 包直接使用次数+1 | |
isRegistedLibName(targetName: string) | 检查是否登记过该 npm 包 | |
isRegistedComponentName(targetName: string) | 检查是否登记过该组件 | |
getComponentNameBelongToLib(targetName: string) | 根据组件名, 查找其隶属的 npm 包名 |
统计 npm 使用情况: UsedLib
记录 npm 包使用记录, 以及 npm 包内组件使用记录
函数签名 | 功能 | 备注 |
---|---|---|
constructor(libName: string) | 初始化 npm 记录, npm 包名为libName |
记录 npm 包使用数据 |
addComponent(componentName: string) | 登记 libName 包中的组件 |
- |
addComponentAlias(componentName: string, componentAliasName: string) | 登记 libName 包中组件的别名 |
- |
incrComponentUseCount(componentName: string, fileUri: string) | 组件在文件fileUri 中使用次数+1 |
- |
incrLibUseCount(fileUri: string) | npm 库在文件fileUri 中使用次数+1 |
npm 包可能本身就是一个函数 |
isRegistedComponentName(testComponentName: string) | 检查组件名testComponentName 是否在libName 包中注册过 |
- |
统计组件使用情况: UsedCompontent
记录组件使用次数
函数签名 | 功能 | 备注 |
---|---|---|
constructor(name: string) | 初始化组件记录对象, name 为被统计组件的名字 | 记录组件使用数据 |
addAliasName(aliasName: string) | 登记组件别名 | - |
incrUseCount(fileUri: string) | 在文件fileUri 中使用次数+1 |
- |
参考资料
👇 介绍了 babel 处理语法树的流程(Visitor 模式), 抽象语法树概念, babel 工作原理, 必读
👇 同上, 也是对 babel 的介绍.
👇 babel 使用 @babel/babel-parser
解析 js 代码, 而 @babel/babel-parser
则是 fork 的acorn
. 处理 babel 生成的抽象语法树时, 必然需要理解每个语法树节点 type 字段的含义(如VariableDeclaration
, ImportDefaultSpecifier
).
标准文档在这里, 不过是英文的, 下边是一份汉语版, 开发过程中可以参考
👇 在线将 js 代码转换为 AST. 编写相关代码时的必备佳品