Author: Yao Zeyuan

Recently, I received a demand to count the popularity of self-developed NPM package & the amount of functions in the package in the front-end project of the company. The solution process is more interesting, here to share.

The basic idea of the project is relatively simple, as shown in the figure below.

For obtaining all front-end project problems, because our company has a set of self-built public front-end packaging platform, we can directly call the platform interface to pull the project source code.

So there is only one difficulty left: how to parse the JS file and get the usage times of exported objects in the target NPM package.

It was simple: we did what Babel did.

As anyone who has used Babel knows, it can read ES6 code, turn the entire JS file into an abstract syntax tree, walk through the tree, call plug-ins to tweak the content of the code, strip/transform the syntax structure, and finally output ES5 code. All we need to do is write a plug-in that identifies the target NPM package and counts the use of variables extracted from the package as Babel traverses the syntax tree. The process is as follows.

Then the rest is manual work: all the syntax tree node types that Babel parses are in the babel-types package, and all you need to do is target each syntax structure in the package (import/variable destruct/rename/function call /…). Write the handler function, and finally output all the results as a JSON.

The code is quite lengthy, so you can browse the Github project for the full text, and just show the data deconstruction for statistics

Summary of project data: SummaryCollection

Create a SummaryCollection object for each project. Call the Add method to register the parsing results for each file

Function signatures function note
constructor() Initialize the summary class Summarize analysis records of all files in the project
add(target: UsedSummaryInFile) Add file analysis data
toJson(): TypeUiLibReport[] Output summary results

File data summary: USedSummaryFile

For a single JS file, the usage record of the target NPM is counted

Function signatures function note
constructor(fileUri: string) Initialize file analysis records Log filesfileUriNPM package uses data in
addLib(libName: string) Once the target NPM is found, register the NPM package name
addLibAlias(libName: string, aliasName: string) Registers the alias of the target NPM package
addComponent(libName: string, componentName: string) Enlists components under the target NPM package
addComponentAlias(libName: string, componentName: string, componentNameAlias: string) Registers the alias of the component under the target NPM package
incrComponentUseCount(libName: string, componentName: string) Number of components used in NPM package +1
incrLibUseCount(libName: string) NPM package direct use times +1
isRegistedLibName(targetName: string) Check whether the NPM package has been registered
isRegistedComponentName(targetName: string) Check whether the component is registered
getComponentNameBelongToLib(targetName: string) Based on the component name, find the NPM package name to which it belongs

NPM usage statistics: UsedLib

Records the usage of NPM packages and the usage of components within NPM packages

Function signatures function note
constructor(libName: string) Initialize NPM record, NPM package namelibName Record NPM package usage data
addComponent(componentName: string) registrationlibNameComponents in packages
addComponentAlias(componentName: string, componentAliasName: string) registrationlibNameAlias of a component in a package
incrComponentUseCount(componentName: string, fileUri: string) Component in filefileUri+1 times of use in
incrLibUseCount(fileUri: string) NPM library in filefileUri+1 times of use in The NPM package may be a function in itself
isRegistedComponentName(testComponentName: string) Checking component namestestComponentNameWhether inlibNamePackage registered

Component usage: UsedCompontent

Record the number of times a component is used

Function signatures function note
constructor(name: string) Initialize the component record object. Name is the name of the component being counted Record component usage data
addAliasName(aliasName: string) Register component alias
incrUseCount(fileUri: string) In the filefileUri+1 times of use in

The resources

👇 introduces Babel to the process of processing syntax tree (Visitor pattern), abstract syntax tree concept, how Babel works, must read

Go deep into Babel. This one will do

👇, also an introduction to Babel.

What front-end engineers need to know about Babel

👇 Babel parses JS code using @babel/babel-parser, while @babel/babel-parser is fork’s Acorn. When working with abstract syntax trees generated by Babel, it is necessary to understand the meaning of the type field of each syntax tree node (e.g., VariableDeclaration, ImportDefaultSpecifier).

Here is the standard document, but in English, below is a Chinese version, you can refer to the development process

Use Acorn to parse JavaScript

👇 convert JS code to AST online. A must-have when writing related code

Abstract syntax tree preview