Skott

All-in-one devtool to automatically analyze, search and visualize dependenc...

README

skott

skott is a minimalist developer tool that can be used to efficiently generate directed graphs from your JavaScript/TypeScript/Node.js project. It can automatically collect metadata such as _file size_, _third-party_ or _builtin dependencies_, detect circular dependencies, finding unused npm dependencies, help you building tools relying on graph data structures thanks to the exposed primitives. Many display modes exists (such as embedded interactive web application shown just below) but also other from the CLI (.svg, .png, .md, .json).

skott

skott exposes directed graphs primitives so that it can be used to implement tools on top of graph structures e.g. affected/incremental patterns as it exposes a way to know precisely and deeply dependencies for each graph node. More generally speaking, anything that can be implemented using graphs can be implemented using skott.

✅ Works for modern JavaScript/TypeScript projects (TSX/JSX, ECMAScript and CommonJS modules all supported). TypeScript's path aliases are also supported.

✅ Deeply detects circular dependencies in an efficient way, with the ability to provide a max depth for the search

✅ File tree traversal supports ignore pattern and Git ignored files (via .gitignore files)

✅ Runs incrementally (experimental), meaning that Skott will _only analyze_ files that were modified/added since the last run, offering performance boosts.

✅ Works with any custom dependency resolver (useful for specific monorepos integration)

✅ Detect unused npm third-party dependencies. Note that all unused devDependencies are not guaranteed to be detected as depcheck only provides analysis for set of supported libraries (eslint, karma, mocha, etc).

✅ Deeply collect all dependencies of the project graph

✅ Deep parent and child dependencies traversals

✅ Metadata collection for each traversed node (file size, view dependencies towards Node.js builtin modules and npm third-party libraries)

✅ Generate static files including raw JSON, mermaid-js diagrams (.svg, .png, .md) representing your project's graph directly generated from the CLI.

skott can be used either via its CLI or JavaScript API. It can either build the project graph using an entrypoint file or build it starting from the current root directory and recursively traverse all directories/folders. Currently, supported files are .js, .jsx, .cjs, .mjs, .ts, .tsx. skott does not rely on module systems for path resolution, it will resolve require/import no matter the configuration.

Getting started


This is a quick start, please check the complete documentation at the skott package level to see more. There are also some examples available there.

Install skott from npm using whatever package manager you like:

  1. ```sh
  2. <insert-your-package-manager> install skott
  3. ```

Let's see examples of an analysis that will traverse all files (minus the ignored ones via the ignorePattern + .gitignore) starting from the current working directory.

Using the CLI

  1. ```sh
  2. skott --displayMode=webapp --trackThirdPartyDependencies --ignorePattern="test/**/*"
  3. ```

Using the API

  1. ```js
  2. import skott from "skott";

  3. const { getStructure, getWorkspace, findUnusedDependencies, useGraph } = await skott({
  4.     ignorePattern: "test/**/*",
  5.     dependencyTracking: {
  6.         builtin: false,
  7.         thirdParty: true,
  8.         typeOnly: true
  9.     }
  10. });

  11. // Do whatever you want with the generated graph
  12. const { graph, files } = getStructure();
  13. const workspace = getWorkspace();
  14. const unusedDependencies = await findUnusedDependencies();
  15. const { findCircularDependencies, collectFilesDependencies, ...traversalApi } = useGraph();
  16. ```

Note: the API is currently published as ESM only.

Because the graph can become heavy on large codebases, you also have the ability to reduce the scope of the analysis:

Specificying a sub-folder

  1. ```sh
  2. skott --cwd=packages/skott
  3. ```

or

  1. ```js
  2. import skott from "skott";

  3. const api = await skott({
  4.     cwd: "packages/skott"
  5. });
  6. ```

Specifying an entrypoint

This will strictly traverse the graph starting from the provided entrypoint, discarding all other files not part of that graph.

  1. ```sh
  2. skott packages/skott/index.ts
  3. ```

or

  1. ```js
  2. import skott from "skott";

  3. const api = await skott({
  4.     entrypoint: "packages/skott/index.ts"
  5. });
  6. ```

Why you should use skott or an equivalent project


The whole purpose of Skott is to build a graph from your project source code and offer many features relying on that generated graph.

Overall, a generated project graph in software engineering acts as a powerful tool that enhances code comprehension, project management, code maintenance and refactoring, and collaboration. It provides a holistic view of the project's structure, dependencies, and relationships, enabling developers to make informed decisions and streamline their development process.

Moreover, Skott aims to provide a comprehensive visual representation of the project's structure, dependencies, and relationships between different components. This visual overview allows developers to better understand the overall architecture and organization of the codebase, making it easier to navigate and identify potential areas of improvement or optimization.

In the essence the main goal of Skott is to help developers understand the codebase's structure and to enable more effective refactoring, reducing code duplication, getting rid of circular dependencies and improving overall code quality.

Why you should care about circular dependencies and dead code


1. Circular (also known as cyclic) dependencies

Let's start with an example of a simple circular dependency between three graph nodes:

In the context of skott, nodes represent JavaScript files.


  1. ```mermaid  
  2.   graph LR
  3.     src/fileA.js --> src/fileB.js
  4.     src/fileB.js --> src/fileC.js
  5.     src/fileC.js --> src/fileA.js
  6. ```

What is the problem with cycles?

Circular dependencies can make your program crash or introduce inconsistencies while modules are being loaded. Node.js module systems try to resolve circular dependencies using different approaches which are more or less successful. For example, CommonJS can (due to its dynamic nature of resolving imports) introduce inconsistencies when building the project graph.

If you're using ECMAScript modules, you can consider yourself safe about module resolution inconsistencies mentioned above, mostly thanks to its static nature.

Nevertheless, cyclic dependencies at the file-level are sometimes choices but more often code smells revealing design misconceptions, so be sure to double check that.

2. Dead code

_Dead code_ can be defined as a code literally having no impact on the application, meaning that removing dead code should not alter in any way the behavior of the system. Some module bundlers such as Rollup and Webpack allow to delete some of the dead code leveraging tree shaking.

However, tree shaking is not an easy task and can mostly work with module systems using static-based imports/exports such as ECMAScript modules. To avoid removing code that appears to be used at runtime, module bundlers are being very precise about determining automatically chunks of code that can be safely removed. Module bundlers can also be helped by providing them manually clues about what can be safely removed e.g. `/#__PURE__/` for Webpack.

If you're not using tools implementing tree shaking, you will be able soon to use skott, which will bring up soon unused imports/exports warnings 🚀  


Graph Management


skott is powered by digraph-js, a _0 dependency_ Node.js library to make Directed Graph construction and traversal effortless.

Parsers


While traversing the project, skott automatically loads the appropriate parser required. When meeting ".js, .cjs, .mjs, .jsx" files, a specific JS parser will be used, otherwise for ".ts, .tsx" files
a specific TS parser will be used instead.

- JavaScript: JavaScript/JSX parsing is done using meriyah
- TypeScript: TypeScript/TSX parsing is done using typescript-estree