Build a Dependency Graph Profiler in JS - Bits and Pieces
source link: https://blog.bitsrc.io/build-a-dependency-graph-profiler-in-js-caf087ce08ea?gi=8141d469f044
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Build a Dependency Graph Profiler in JS
Build a Dependency Graph Profiler in JS
Understand dependency graphs in JavaScript
Breaking our app utilities and UI components into files brings modularity to our project. This helps us keep code organized in separate, reusable files, which improves code maintainability.
Each file in the module system exposes its API to the public by using the exports or export keyword. Then, we can reference them by using the require or import keyword.
Any file that we import or require becomes a dependency on the file and as such, we build up a dependency chain that can span a thousand files.
Bundling systems like Webpack use these dependency chains to know which files are to be bundled because the entry file won’t work without them.
webpack
webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also…
An example of smart use of dependency graphs can be found in Bit. Bit isolates your app’s modules/components by tracking your files’ dependencies. This component isolation enables sharing and collaborating on individual components instead of the old way of collaborating solely on full monolithic projects constructed out of unintelligible files.
Example: shared JS utility functions automatically isolated and pushed to Bit’s cloud
In this post, we will see how to build a dependency chain or graph of any file using JavaScript.
How can we construct this graph?
Simple, starting from an entry point (such as main1.js
above), we will look for all of the dependencies (other pieces of code that it needs to function) of the file main1.js
and construct a graph.
The graph structure gets built up through recursively checking for dependencies within.
Now, how can we achieve that in JS?
First, we get the AST node of the entry file. Now, we see what dependencies of a file are imported using the import
keyword or require
function call.
Now, these keyword/function calls are represented in the ES tree in different ways.
The import
keyword translates to this in EStree:
ImportDeclaration
interface ImportDeclaration {
type: "ImportDeclaration";
specifiers: [ ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier ];
source: Literal;
}
The above represents an import declaration, e.g., import foo from "mod";
. The type
property tells us the Node is an ImportDeclaration
node. The source is a Node Literal that holds the file name we are importing from.
We have specifiers, we have three kinds of specifiers
ImportSpecifier
interface ImportSpecifier {
type: "ImportSpecifier";
imported: Identifier;
}
An imported variable binding, e.g., {foo}
in import {foo} from "mod"
or {foo as bar}
in import {foo as bar} from "mod"
. The imported
field refers to the name of the export imported from the module. The local
field refers to the binding imported into the local module scope. If it is a basic named import, such as in import {foo} from "mod"
, both imported
and local
are equivalent Identifier
nodes; in this case, an Identifier
node representing foo
. If it is an aliased import, such as in import {foo as bar} from "mod"
, the imported
field is an Identifier
node representing foo
, and the local
field is an Identifier
node representing bar
.
ImportDefaultSpecifier
interface ImportDefaultSpecifier {
type: "ImportDefaultSpecifier";
}
A default import specifier, e.g., foo
in import foo from "mod.js"
.
ImportNamespaceSpecifier
interface ImportNamespaceSpecifier {
type: "ImportNamespaceSpecifier";
}
A namespace import specifier, e.g., * as foo
in import * as foo from "mod.js"
.
The require
is a call so it will be a Node CallExpression
:
interface CallExpression {
type: "CallExpression";
callee: Expression;
arguments: [ Expression ];
}
Now, we have seen the Node of the import
keyword and require
function, what we will do next is to walk through the AST generated, and add filters for the ImportDeclaration
and CallExpression
nodes. From the filter, we can grab the file dependencies and recursively through the filter to gather the dependencies within.
Let’s start
We first create a folder depGraph
and init a node environment:
mkdir depGraph
cd depGraph
npm init -y
We need a library that can parse and walk through the generated AST or create our own. For this post, we will create our own JS AST walker, JSEmitter. We will generate AST with acorn and use our JSEmitter to walk through the AST.
We install acorn
library: npm i acorn
. Next we create jsemitter.js file. We fill it we the below code:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK