npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

esm-imports-analyzer

v0.1.7

Published

A tool to generate an HTML visualization of the imports of an ESM Node.js application. It executes the application, tracks the imports during runtime, and creates the visualization after the execution is complete.

Readme

ESM Imports Analyzer

A CLI tool that captures all ESM imports during a Node.js application's execution and generates a self-contained HTML report with an interactive dependency graph and timing data.

Live demo

Live demo

Usage

Requires Node.js 24+.

Install

npm install -D esm-imports-analyzer

Run

npx esm-imports-analyzer -- node app.js

The -- separator is required. Everything after it is the command to analyze.

# Analyze a test suite
npx esm-imports-analyzer -- node --test test/run-all.js

# Analyze a CLI tool
npx esm-imports-analyzer -- node_modules/.bin/tool

# Custom output path
npx esm-imports-analyzer -o report.html -- node app.js

Avoid using npx, pnpm, bunx, etc. after -- — they are Node.js processes themselves and will be measured. Use node_modules/.bin/<tool> or node <script> directly instead.

Options

--output, -o <path>   Output HTML report path (default: ./esm-imports-report.html)
--help, -h            Show help
--version, -v         Show version

Features

The generated report is a single HTML file with:

  • Dependency graph — interactive visualization of all module imports, grouped by package
  • Scalable to large graphs — all packages start collapsed, showing only inter-package edges. Expand individual packages to reveal their modules and folder hierarchy, keeping the visible graph as simple or detailed as you need
  • Import time — wall-clock time to fully import each module (resolve + load + parse + dependencies + top-level execution)
  • Circular dependency detection — easily spot and copy circular import chains
  • Slowest modules table — sortable by import time or recursive import count
  • Search — highlight matching modules across the graph

Graph interactions

| Action | Effect | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------- | | Click a node | Select it. Highlights outgoing imports (blue), incoming importers (green), and cycle edges (yellow). Everything else dims. | | Shift-click | Toggle a node in/out of the selection without affecting other selected nodes. | | Double-click a package | Expand or collapse it. | | Double-click a folder | Expand it (show its children inside the package). | | Double-click a module | Zoom into it. | | Double-click empty space | Zoom to fit the entire graph. | | Right-click any node | Context menu: expand importer/imported files, copy absolute path, copy full import chain from roots seen during execution | | Hover a module | Tooltip with full path and import time. |

Other UI elements

  • Cycles panel (left sidebar) — lists all circular dependencies. Click one to reveal and highlight its members with orange.
  • Table (bottom, resizable) — shows the slowest modules. Click a row to focus on it in the graph. Sortable by import time, import count, or name.
  • Expand all / Collapse all — buttons in the header.
  • Auto re-layout — checkbox (default: on). When enabled, the graph layout recomputes automatically after every expand/collapse. Disable it to manually control when layout runs via the Re-layout button. Useful for very large graphs where automatic layout can be slow.

How it works

  1. The CLI prepends --import=<register.ts> to NODE_OPTIONS and spawns your command as a child process. This injects a loader hook into the Node.js runtime without modifying your code.

  2. The loader uses module.registerHooks() (Node 24's in-thread hooks API) to intercept every resolve and load call. On each resolve, it records a timestamp. On each load, it appends a small callback invocation to the module's source code:

    // Your module's source
    import { foo } from "./bar.js";
    doSomething();
    
    // Injected at the end:
    globalThis.__esm_analyzer_import_done__("<module-url>");
  3. When the module finishes evaluating — after all its static dependencies have been resolved, loaded, and evaluated, and its own top-level code has run — the injected callback fires and records the elapsed time since the resolve hook started. This gives the total import time: the full wall-clock cost of importing that module, including everything it triggers.

  4. On process exit, all collected data is written to a temporary JSON file.

  5. The CLI reads that file and runs the analysis pipeline: builds the import tree, detects cycles (Tarjan's SCC), groups modules by package, computes the folder hierarchy, and ranks modules by import time.

  6. The result is assembled into a single self-contained HTML file. The graph is rendered client-side using Cytoscape.js with dagre layout computed in a Web Worker.

Known limitations

  • Node.js 24+ required — uses module.registerHooks(), available since Node 24.
  • ESM entry point required — the project being analyzed must use ESM ("type": "module" or .mjs entry). CJS modules imported from ESM are measured correctly.
  • Modules that throw — if a module throws during evaluation, the injected callback never runs, so its import time won't be recorded.
  • Top-level await — import time includes time spent suspended in await. This is technically correct (it's real time your app waits) but can make async modules look disproportionately slow.
  • Process must exit cleanly — import data is flushed via beforeExit/exit event handlers. If the process is killed with SIGKILL or exits in a way that skips these handlers, data may be lost.
  • CDN dependency — the HTML report loads Cytoscape.js from unpkg on first open. After that, the browser cache handles it.

License

MIT - Patricio Palladino