esm-imports-analyzer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/dist/analysis/cycle-detector.d.ts +3 -0
- package/dist/analysis/cycle-detector.d.ts.map +1 -0
- package/dist/analysis/cycle-detector.js +111 -0
- package/dist/analysis/cycle-detector.js.map +1 -0
- package/dist/analysis/folder-tree.d.ts +3 -0
- package/dist/analysis/folder-tree.d.ts.map +1 -0
- package/dist/analysis/folder-tree.js +141 -0
- package/dist/analysis/folder-tree.js.map +1 -0
- package/dist/analysis/grouper.d.ts +4 -0
- package/dist/analysis/grouper.d.ts.map +1 -0
- package/dist/analysis/grouper.js +156 -0
- package/dist/analysis/grouper.js.map +1 -0
- package/dist/analysis/timing.d.ts +9 -0
- package/dist/analysis/timing.d.ts.map +1 -0
- package/dist/analysis/timing.js +37 -0
- package/dist/analysis/timing.js.map +1 -0
- package/dist/analysis/tree-builder.d.ts +3 -0
- package/dist/analysis/tree-builder.d.ts.map +1 -0
- package/dist/analysis/tree-builder.js +47 -0
- package/dist/analysis/tree-builder.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +184 -0
- package/dist/cli.js.map +1 -0
- package/dist/loader/hooks.d.ts +4 -0
- package/dist/loader/hooks.d.ts.map +1 -0
- package/dist/loader/hooks.js +79 -0
- package/dist/loader/hooks.js.map +1 -0
- package/dist/loader/register.d.ts +2 -0
- package/dist/loader/register.d.ts.map +1 -0
- package/dist/loader/register.js +35 -0
- package/dist/loader/register.js.map +1 -0
- package/dist/report/generator.d.ts +3 -0
- package/dist/report/generator.d.ts.map +1 -0
- package/dist/report/generator.js +50 -0
- package/dist/report/generator.js.map +1 -0
- package/dist/report/template.html +146 -0
- package/dist/report/ui/cycles-panel.js +80 -0
- package/dist/report/ui/filters.js +13 -0
- package/dist/report/ui/graph.js +1310 -0
- package/dist/report/ui/styles.css +531 -0
- package/dist/report/ui/table.js +209 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# ESM Imports Analyzer
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
[Live demo](TODO)
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Requires **Node.js 24+**.
|
|
12
|
+
|
|
13
|
+
### Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -D esm-imports-analyzer
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Run
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx esm-imports-analyzer -- node app.js
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The `--` separator is required. Everything after it is the command to analyze.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Analyze a test suite
|
|
29
|
+
npx esm-imports-analyzer -- node --test test/run-all.js
|
|
30
|
+
|
|
31
|
+
# Analyze a CLI tool
|
|
32
|
+
npx esm-imports-analyzer -- node_modules/.bin/hardhat compile
|
|
33
|
+
|
|
34
|
+
# Custom output path
|
|
35
|
+
npx esm-imports-analyzer -o report.html -- node app.js
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> **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.
|
|
39
|
+
|
|
40
|
+
### Options
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
--output, -o <path> Output HTML report path (default: ./esm-imports-report.html)
|
|
44
|
+
--help, -h Show help
|
|
45
|
+
--version, -v Show version
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
The generated report is a single HTML file with:
|
|
51
|
+
|
|
52
|
+
- **Dependency graph** — interactive visualization of all module imports, grouped by package
|
|
53
|
+
- **Import time** — wall-clock time to fully import each module (resolve + load + parse + dependencies + top-level execution)
|
|
54
|
+
- **Circular dependency detection** — found via Tarjan's strongly connected components algorithm
|
|
55
|
+
- **Package grouping** — modules grouped by `package.json` boundaries with collapsible folder hierarchy
|
|
56
|
+
- **Slowest modules table** — sortable by import time or recursive import count
|
|
57
|
+
- **Search** — highlight matching modules across the graph
|
|
58
|
+
|
|
59
|
+
### Graph interactions
|
|
60
|
+
|
|
61
|
+
| Action | Effect |
|
|
62
|
+
|--------|--------|
|
|
63
|
+
| Click a node | Select it. Highlights outgoing imports (blue), incoming importers (green), and cycle edges (yellow). Everything else dims. |
|
|
64
|
+
| Shift-click | Toggle a node in/out of the selection without affecting other selected nodes. |
|
|
65
|
+
| Double-click a package | Expand or collapse it. |
|
|
66
|
+
| Double-click a folder | Expand it (show its children inside the package). |
|
|
67
|
+
| Double-click a module | Zoom into it. |
|
|
68
|
+
| Double-click empty space | Zoom to fit the entire graph. |
|
|
69
|
+
| Right-click any node | Context menu: expand importer/imported files, copy absolute path, copy full import chain from root. |
|
|
70
|
+
| Hover a module | Tooltip with full path and import time. |
|
|
71
|
+
|
|
72
|
+
### Other UI elements
|
|
73
|
+
|
|
74
|
+
- **Cycles panel** (left sidebar) — lists all circular dependencies. Click one to reveal and highlight its members with orange.
|
|
75
|
+
- **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.
|
|
76
|
+
- **Expand all / Collapse all** — buttons in the header.
|
|
77
|
+
- **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.
|
|
78
|
+
|
|
79
|
+
## How it works
|
|
80
|
+
|
|
81
|
+
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.
|
|
82
|
+
|
|
83
|
+
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:
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
// Your module's source
|
|
87
|
+
import { foo } from './bar.js';
|
|
88
|
+
doSomething();
|
|
89
|
+
|
|
90
|
+
// Injected at the end:
|
|
91
|
+
;globalThis.__esm_analyzer_import_done__("<module-url>");
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
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.
|
|
95
|
+
|
|
96
|
+
4. On process exit, all collected data is written to a temporary JSON file.
|
|
97
|
+
|
|
98
|
+
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.
|
|
99
|
+
|
|
100
|
+
6. The result is assembled into a single self-contained HTML file. The graph is rendered client-side using [Cytoscape.js](https://js.cytoscape.org/) with [dagre](https://github.com/dagrejs/dagre) layout computed in a Web Worker.
|
|
101
|
+
|
|
102
|
+
## Known limitations
|
|
103
|
+
|
|
104
|
+
- **Node.js 24+ required** — uses `module.registerHooks()`, available since Node 24.
|
|
105
|
+
- **ESM entry point required** — the project being analyzed must use ESM (`"type": "module"` or `.mjs` entry). CJS modules imported from ESM are measured correctly.
|
|
106
|
+
- **Modules that throw** — if a module throws during evaluation, the injected callback never runs, so its import time won't be recorded.
|
|
107
|
+
- **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.
|
|
108
|
+
- **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.
|
|
109
|
+
- **CDN dependency** — the HTML report loads Cytoscape.js from [unpkg](https://unpkg.com/) on first open. After that, the browser cache handles it.
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT - Patricio Palladino
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cycle-detector.d.ts","sourceRoot":"","sources":["../../src/analysis/cycle-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAiHvD,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,KAAK,EAAE,CAa7D"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Build adjacency list from import records
|
|
2
|
+
function buildAdjacencyList(records) {
|
|
3
|
+
const graph = new Map();
|
|
4
|
+
for (const record of records) {
|
|
5
|
+
if (!graph.has(record.resolvedURL)) {
|
|
6
|
+
graph.set(record.resolvedURL, new Set());
|
|
7
|
+
}
|
|
8
|
+
if (record.parentURL !== null) {
|
|
9
|
+
if (!graph.has(record.parentURL)) {
|
|
10
|
+
graph.set(record.parentURL, new Set());
|
|
11
|
+
}
|
|
12
|
+
graph.get(record.parentURL).add(record.resolvedURL);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return graph;
|
|
16
|
+
}
|
|
17
|
+
// Tarjan's SCC algorithm
|
|
18
|
+
function tarjanSCC(graph) {
|
|
19
|
+
let index = 0;
|
|
20
|
+
const stack = [];
|
|
21
|
+
const onStack = new Set();
|
|
22
|
+
const indices = new Map();
|
|
23
|
+
const lowlinks = new Map();
|
|
24
|
+
const sccs = [];
|
|
25
|
+
function strongconnect(v) {
|
|
26
|
+
indices.set(v, index);
|
|
27
|
+
lowlinks.set(v, index);
|
|
28
|
+
index++;
|
|
29
|
+
stack.push(v);
|
|
30
|
+
onStack.add(v);
|
|
31
|
+
const neighbors = graph.get(v) ?? new Set();
|
|
32
|
+
for (const w of neighbors) {
|
|
33
|
+
if (!indices.has(w)) {
|
|
34
|
+
strongconnect(w);
|
|
35
|
+
lowlinks.set(v, Math.min(lowlinks.get(v), lowlinks.get(w)));
|
|
36
|
+
}
|
|
37
|
+
else if (onStack.has(w)) {
|
|
38
|
+
lowlinks.set(v, Math.min(lowlinks.get(v), indices.get(w)));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (lowlinks.get(v) === indices.get(v)) {
|
|
42
|
+
const scc = [];
|
|
43
|
+
let w;
|
|
44
|
+
do {
|
|
45
|
+
w = stack.pop();
|
|
46
|
+
onStack.delete(w);
|
|
47
|
+
scc.push(w);
|
|
48
|
+
} while (w !== v);
|
|
49
|
+
sccs.push(scc);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const v of graph.keys()) {
|
|
53
|
+
if (!indices.has(v)) {
|
|
54
|
+
strongconnect(v);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return sccs;
|
|
58
|
+
}
|
|
59
|
+
// Extract individual cycles from SCCs using DFS
|
|
60
|
+
function extractCycles(scc, graph) {
|
|
61
|
+
if (scc.length === 1) {
|
|
62
|
+
const node = scc[0];
|
|
63
|
+
const neighbors = graph.get(node) ?? new Set();
|
|
64
|
+
if (neighbors.has(node)) {
|
|
65
|
+
return [{ modules: [node], length: 1 }];
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const sccSet = new Set(scc);
|
|
70
|
+
const cycles = [];
|
|
71
|
+
const visited = new Set();
|
|
72
|
+
function dfs(start, current, path) {
|
|
73
|
+
const neighbors = graph.get(current) ?? new Set();
|
|
74
|
+
for (const next of neighbors) {
|
|
75
|
+
if (!sccSet.has(next))
|
|
76
|
+
continue;
|
|
77
|
+
if (next === start && path.length > 0) {
|
|
78
|
+
cycles.push({ modules: [...path], length: path.length });
|
|
79
|
+
}
|
|
80
|
+
else if (!visited.has(next) && !path.includes(next)) {
|
|
81
|
+
dfs(start, next, [...path, next]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const node of scc) {
|
|
86
|
+
visited.clear();
|
|
87
|
+
dfs(node, node, [node]);
|
|
88
|
+
visited.add(node);
|
|
89
|
+
}
|
|
90
|
+
// Deduplicate cycles (same set of nodes in different rotations)
|
|
91
|
+
const unique = new Map();
|
|
92
|
+
for (const cycle of cycles) {
|
|
93
|
+
const key = [...cycle.modules].sort().join('\0');
|
|
94
|
+
if (!unique.has(key) || cycle.length < unique.get(key).length) {
|
|
95
|
+
unique.set(key, cycle);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return [...unique.values()];
|
|
99
|
+
}
|
|
100
|
+
export function detectCycles(records) {
|
|
101
|
+
const graph = buildAdjacencyList(records);
|
|
102
|
+
const sccs = tarjanSCC(graph);
|
|
103
|
+
const cycles = [];
|
|
104
|
+
for (const scc of sccs) {
|
|
105
|
+
cycles.push(...extractCycles(scc, graph));
|
|
106
|
+
}
|
|
107
|
+
// Sort by cycle length (shortest first)
|
|
108
|
+
cycles.sort((a, b) => a.length - b.length);
|
|
109
|
+
return cycles;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=cycle-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cycle-detector.js","sourceRoot":"","sources":["../../src/analysis/cycle-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folder-tree.d.ts","sourceRoot":"","sources":["../../src/analysis/folder-tree.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAmGlD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAkD3G"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { relative } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
function makeRawFolder(name) {
|
|
4
|
+
return { name, children: new Map() };
|
|
5
|
+
}
|
|
6
|
+
function insertPath(root, segments, moduleURL) {
|
|
7
|
+
let current = root;
|
|
8
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
9
|
+
const seg = segments[i];
|
|
10
|
+
if (!current.children.has(seg)) {
|
|
11
|
+
current.children.set(seg, makeRawFolder(seg));
|
|
12
|
+
}
|
|
13
|
+
current = current.children.get(seg);
|
|
14
|
+
}
|
|
15
|
+
const fileName = segments[segments.length - 1];
|
|
16
|
+
current.children.set(fileName, {
|
|
17
|
+
name: fileName,
|
|
18
|
+
children: new Map(),
|
|
19
|
+
moduleURL,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function sortNodes(nodes) {
|
|
23
|
+
return nodes.sort((a, b) => {
|
|
24
|
+
if (a.type !== b.type)
|
|
25
|
+
return a.type === 'folder' ? -1 : 1;
|
|
26
|
+
return a.label.localeCompare(b.label);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// Convert a raw trie node into a FolderTreeNode (no flattening yet)
|
|
30
|
+
function rawToTree(groupId, node, relPath) {
|
|
31
|
+
if (node.moduleURL !== undefined) {
|
|
32
|
+
return {
|
|
33
|
+
id: node.moduleURL,
|
|
34
|
+
label: node.name,
|
|
35
|
+
type: 'file',
|
|
36
|
+
moduleURL: node.moduleURL,
|
|
37
|
+
children: [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const children = [];
|
|
41
|
+
for (const child of node.children.values()) {
|
|
42
|
+
const childRel = relPath ? relPath + '/' + child.name : child.name;
|
|
43
|
+
children.push(rawToTree(groupId, child, childRel));
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
id: 'ftree::' + groupId + '::' + relPath,
|
|
47
|
+
label: node.name,
|
|
48
|
+
type: 'folder',
|
|
49
|
+
children: sortNodes(children),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Flatten a list of sibling nodes. For each folder that has exactly 1 child,
|
|
53
|
+
// merge it with that child (concatenating labels). Repeat until stable.
|
|
54
|
+
function flattenChildren(groupId, nodes) {
|
|
55
|
+
return sortNodes(nodes.map(node => flattenSingle(groupId, node)));
|
|
56
|
+
}
|
|
57
|
+
function joinLabel(a, b) {
|
|
58
|
+
return a ? a + '/' + b : b;
|
|
59
|
+
}
|
|
60
|
+
function flattenSingle(groupId, node) {
|
|
61
|
+
if (node.type === 'file')
|
|
62
|
+
return node;
|
|
63
|
+
let current = node;
|
|
64
|
+
// Walk down through single-child folders, accumulating the path
|
|
65
|
+
while (current.type === 'folder' && current.children.length === 1) {
|
|
66
|
+
const only = current.children[0];
|
|
67
|
+
const merged = joinLabel(current.label, only.label);
|
|
68
|
+
if (only.type === 'file') {
|
|
69
|
+
return { ...only, label: merged };
|
|
70
|
+
}
|
|
71
|
+
// Single folder child: merge labels and continue
|
|
72
|
+
current = {
|
|
73
|
+
...only,
|
|
74
|
+
label: merged,
|
|
75
|
+
id: 'ftree::' + groupId + '::' + merged,
|
|
76
|
+
children: only.children,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// current now has 0 or 2+ children — flatten each child recursively
|
|
80
|
+
return {
|
|
81
|
+
...current,
|
|
82
|
+
children: flattenChildren(groupId, current.children),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function buildFolderTree(groupId, packageDir, moduleURLs) {
|
|
86
|
+
const root = makeRawFolder('');
|
|
87
|
+
for (const url of moduleURLs) {
|
|
88
|
+
if (!url.startsWith('file://'))
|
|
89
|
+
continue;
|
|
90
|
+
let filePath;
|
|
91
|
+
try {
|
|
92
|
+
filePath = fileURLToPath(url);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const rel = relative(packageDir, filePath);
|
|
98
|
+
if (rel.startsWith('..') || rel.startsWith('/'))
|
|
99
|
+
continue;
|
|
100
|
+
const segments = rel.split('/').filter(s => s.length > 0);
|
|
101
|
+
if (segments.length === 0)
|
|
102
|
+
continue;
|
|
103
|
+
insertPath(root, segments, url);
|
|
104
|
+
}
|
|
105
|
+
// Convert to typed tree (root is virtual, we take its children)
|
|
106
|
+
const topLevel = [];
|
|
107
|
+
for (const child of root.children.values()) {
|
|
108
|
+
topLevel.push(rawToTree(groupId, child, child.name));
|
|
109
|
+
}
|
|
110
|
+
// Treat the virtual root like a folder and apply the same flatten logic:
|
|
111
|
+
// if exactly 1 child, merge through it.
|
|
112
|
+
const virtualRoot = {
|
|
113
|
+
id: '',
|
|
114
|
+
label: '',
|
|
115
|
+
type: 'folder',
|
|
116
|
+
children: sortNodes(topLevel),
|
|
117
|
+
};
|
|
118
|
+
const flattened = flattenSingle(groupId, virtualRoot);
|
|
119
|
+
if (flattened.type === 'file') {
|
|
120
|
+
// Entire package is a single file
|
|
121
|
+
return [flattened];
|
|
122
|
+
}
|
|
123
|
+
// If the virtual root was flattened (label is non-empty), we absorbed
|
|
124
|
+
// intermediate folders. Prefix all children's labels with that path.
|
|
125
|
+
if (flattened.label) {
|
|
126
|
+
return flattened.children.map(child => prefixNode(groupId, flattened.label, child));
|
|
127
|
+
}
|
|
128
|
+
return flattened.children;
|
|
129
|
+
}
|
|
130
|
+
function prefixNode(groupId, prefix, node) {
|
|
131
|
+
const label = prefix + '/' + node.label;
|
|
132
|
+
if (node.type === 'file') {
|
|
133
|
+
return { ...node, label };
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
...node,
|
|
137
|
+
label,
|
|
138
|
+
id: 'ftree::' + groupId + '::' + label,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=folder-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folder-tree.js","sourceRoot":"","sources":["../../src/analysis/folder-tree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grouper.d.ts","sourceRoot":"","sources":["../../src/analysis/grouper.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AA4EvD,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,KAAK,EAAE,CA2F7D;AAGD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { buildFolderTree } from "./folder-tree.js";
|
|
5
|
+
const packageJsonCache = new Map();
|
|
6
|
+
function getNodeModulesPackageRoot(dir) {
|
|
7
|
+
const SEP = '/node_modules/';
|
|
8
|
+
const lastNM = dir.lastIndexOf(SEP);
|
|
9
|
+
if (lastNM === -1)
|
|
10
|
+
return null;
|
|
11
|
+
const afterNM = dir.substring(lastNM + SEP.length);
|
|
12
|
+
const parts = afterNM.split('/');
|
|
13
|
+
if (parts.length === 0 || parts[0] === '')
|
|
14
|
+
return null;
|
|
15
|
+
// Scoped package: @scope/pkg
|
|
16
|
+
if (parts[0].startsWith('@')) {
|
|
17
|
+
if (parts.length < 2)
|
|
18
|
+
return null;
|
|
19
|
+
return dir.substring(0, lastNM) + SEP + parts[0] + '/' + parts[1];
|
|
20
|
+
}
|
|
21
|
+
// Regular package: pkg
|
|
22
|
+
return dir.substring(0, lastNM) + SEP + parts[0];
|
|
23
|
+
}
|
|
24
|
+
function findPackageJson(dir) {
|
|
25
|
+
if (packageJsonCache.has(dir)) {
|
|
26
|
+
return packageJsonCache.get(dir);
|
|
27
|
+
}
|
|
28
|
+
// For paths inside node_modules, jump directly to the package root
|
|
29
|
+
// instead of walking up and potentially hitting nested package.json files.
|
|
30
|
+
const nmRoot = getNodeModulesPackageRoot(dir);
|
|
31
|
+
if (nmRoot !== null && nmRoot !== dir) {
|
|
32
|
+
const result = findPackageJson(nmRoot);
|
|
33
|
+
packageJsonCache.set(dir, result);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
const pkgPath = join(dir, 'package.json');
|
|
37
|
+
if (existsSync(pkgPath)) {
|
|
38
|
+
try {
|
|
39
|
+
const content = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
40
|
+
const name = (typeof content['name'] === 'string' ? content['name'] : dirname(dir).split('/').pop());
|
|
41
|
+
const isNodeModules = dir.includes('node_modules');
|
|
42
|
+
const info = {
|
|
43
|
+
name,
|
|
44
|
+
dir,
|
|
45
|
+
packageJsonPath: pkgPath,
|
|
46
|
+
isNodeModules,
|
|
47
|
+
};
|
|
48
|
+
packageJsonCache.set(dir, info);
|
|
49
|
+
return info;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Invalid package.json, keep searching
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const parent = dirname(dir);
|
|
56
|
+
if (parent === dir) {
|
|
57
|
+
// Reached filesystem root
|
|
58
|
+
packageJsonCache.set(dir, null);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const result = findPackageJson(parent);
|
|
62
|
+
packageJsonCache.set(dir, result);
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
export function groupModules(records) {
|
|
66
|
+
const groupMap = new Map();
|
|
67
|
+
const seenURLs = new Set();
|
|
68
|
+
for (const record of records) {
|
|
69
|
+
const url = record.resolvedURL;
|
|
70
|
+
if (seenURLs.has(url))
|
|
71
|
+
continue;
|
|
72
|
+
seenURLs.add(url);
|
|
73
|
+
// Handle node: builtins
|
|
74
|
+
if (url.startsWith('node:')) {
|
|
75
|
+
const id = 'node-builtins';
|
|
76
|
+
if (!groupMap.has(id)) {
|
|
77
|
+
groupMap.set(id, {
|
|
78
|
+
id,
|
|
79
|
+
label: 'Node.js Builtins',
|
|
80
|
+
packageJsonPath: '',
|
|
81
|
+
modules: [],
|
|
82
|
+
isNodeModules: false,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
groupMap.get(id).modules.push(url);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// Handle data: URLs
|
|
89
|
+
if (url.startsWith('data:')) {
|
|
90
|
+
const id = 'inline-modules';
|
|
91
|
+
if (!groupMap.has(id)) {
|
|
92
|
+
groupMap.set(id, {
|
|
93
|
+
id,
|
|
94
|
+
label: 'Inline Modules',
|
|
95
|
+
packageJsonPath: '',
|
|
96
|
+
modules: [],
|
|
97
|
+
isNodeModules: false,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
groupMap.get(id).modules.push(url);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
// Handle file: URLs
|
|
104
|
+
if (url.startsWith('file://')) {
|
|
105
|
+
let filePath;
|
|
106
|
+
try {
|
|
107
|
+
filePath = fileURLToPath(url);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const dir = dirname(filePath);
|
|
113
|
+
const pkgInfo = findPackageJson(dir);
|
|
114
|
+
if (pkgInfo) {
|
|
115
|
+
const id = resolve(pkgInfo.dir);
|
|
116
|
+
if (!groupMap.has(id)) {
|
|
117
|
+
groupMap.set(id, {
|
|
118
|
+
id,
|
|
119
|
+
label: pkgInfo.name,
|
|
120
|
+
packageJsonPath: pkgInfo.packageJsonPath,
|
|
121
|
+
modules: [],
|
|
122
|
+
isNodeModules: pkgInfo.isNodeModules,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
groupMap.get(id).modules.push(url);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
const id = 'ungrouped';
|
|
129
|
+
if (!groupMap.has(id)) {
|
|
130
|
+
groupMap.set(id, {
|
|
131
|
+
id,
|
|
132
|
+
label: 'Ungrouped',
|
|
133
|
+
packageJsonPath: '',
|
|
134
|
+
modules: [],
|
|
135
|
+
isNodeModules: false,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
groupMap.get(id).modules.push(url);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const groups = [...groupMap.values()];
|
|
143
|
+
// Build folder trees for groups that have a package directory
|
|
144
|
+
for (const group of groups) {
|
|
145
|
+
if (group.packageJsonPath) {
|
|
146
|
+
const packageDir = dirname(group.packageJsonPath);
|
|
147
|
+
group.folderTree = buildFolderTree(group.id, packageDir, group.modules);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return groups;
|
|
151
|
+
}
|
|
152
|
+
// Clear the cache (useful for testing)
|
|
153
|
+
export function clearPackageJsonCache() {
|
|
154
|
+
packageJsonCache.clear();
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=grouper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grouper.js","sourceRoot":"","sources":["../../src/analysis/grouper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ImportRecord } from '../types.ts';
|
|
2
|
+
export interface TimingEntry {
|
|
3
|
+
resolvedURL: string;
|
|
4
|
+
specifier: string;
|
|
5
|
+
totalTime: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function computeRankedList(records: ImportRecord[]): TimingEntry[];
|
|
8
|
+
export declare function computeTotalTime(records: ImportRecord[]): number;
|
|
9
|
+
//# sourceMappingURL=timing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../../src/analysis/timing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,WAAW,EAAE,CAoBxE;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAahE"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function computeRankedList(records) {
|
|
2
|
+
// Use first occurrence of each module (subsequent imports are cached)
|
|
3
|
+
const seen = new Set();
|
|
4
|
+
const entries = [];
|
|
5
|
+
for (const record of records) {
|
|
6
|
+
if (!seen.has(record.resolvedURL)) {
|
|
7
|
+
seen.add(record.resolvedURL);
|
|
8
|
+
entries.push({
|
|
9
|
+
resolvedURL: record.resolvedURL,
|
|
10
|
+
specifier: record.specifier,
|
|
11
|
+
totalTime: record.totalImportTime ?? 0,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// Sort descending by totalTime, stable sort preserves insertion order for equal times
|
|
16
|
+
entries.sort((a, b) => b.totalTime - a.totalTime);
|
|
17
|
+
return entries;
|
|
18
|
+
}
|
|
19
|
+
export function computeTotalTime(records) {
|
|
20
|
+
if (records.length === 0)
|
|
21
|
+
return 0;
|
|
22
|
+
let minStart = Infinity;
|
|
23
|
+
let maxEnd = -Infinity;
|
|
24
|
+
for (const record of records) {
|
|
25
|
+
if (record.importStartTime < minStart)
|
|
26
|
+
minStart = record.importStartTime;
|
|
27
|
+
if (record.totalImportTime !== undefined) {
|
|
28
|
+
const end = record.importStartTime + record.totalImportTime;
|
|
29
|
+
if (end > maxEnd)
|
|
30
|
+
maxEnd = end;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (maxEnd === -Infinity)
|
|
34
|
+
return 0;
|
|
35
|
+
return maxEnd - minStart;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=timing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/analysis/timing.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,iBAAiB,CAAC,OAAuB;IACvD,sEAAsE;IACtE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,OAAO,GAAkB,EAAE,CAAC;IAElC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YAC7B,OAAO,CAAC,IAAI,CAAC;gBACX,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,SAAS,EAAE,MAAM,CAAC,eAAe,IAAI,CAAC;aACvC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,sFAAsF;IACtF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IAElD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAuB;IACtD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACnC,IAAI,QAAQ,GAAG,QAAQ,CAAC;IACxB,IAAI,MAAM,GAAG,CAAC,QAAQ,CAAC;IACvB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,eAAe,GAAG,QAAQ;YAAE,QAAQ,GAAG,MAAM,CAAC,eAAe,CAAC;QACzE,IAAI,MAAM,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;YACzC,MAAM,GAAG,GAAG,MAAM,CAAC,eAAe,GAAG,MAAM,CAAC,eAAe,CAAC;YAC5D,IAAI,GAAG,GAAG,MAAM;gBAAE,MAAM,GAAG,GAAG,CAAC;QACjC,CAAC;IACH,CAAC;IACD,IAAI,MAAM,KAAK,CAAC,QAAQ;QAAE,OAAO,CAAC,CAAC;IACnC,OAAO,MAAM,GAAG,QAAQ,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-builder.d.ts","sourceRoot":"","sources":["../../src/analysis/tree-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE5D,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,UAAU,EAAE,CA+C/D"}
|