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.
Files changed (48) hide show
  1. package/README.md +113 -0
  2. package/dist/analysis/cycle-detector.d.ts +3 -0
  3. package/dist/analysis/cycle-detector.d.ts.map +1 -0
  4. package/dist/analysis/cycle-detector.js +111 -0
  5. package/dist/analysis/cycle-detector.js.map +1 -0
  6. package/dist/analysis/folder-tree.d.ts +3 -0
  7. package/dist/analysis/folder-tree.d.ts.map +1 -0
  8. package/dist/analysis/folder-tree.js +141 -0
  9. package/dist/analysis/folder-tree.js.map +1 -0
  10. package/dist/analysis/grouper.d.ts +4 -0
  11. package/dist/analysis/grouper.d.ts.map +1 -0
  12. package/dist/analysis/grouper.js +156 -0
  13. package/dist/analysis/grouper.js.map +1 -0
  14. package/dist/analysis/timing.d.ts +9 -0
  15. package/dist/analysis/timing.d.ts.map +1 -0
  16. package/dist/analysis/timing.js +37 -0
  17. package/dist/analysis/timing.js.map +1 -0
  18. package/dist/analysis/tree-builder.d.ts +3 -0
  19. package/dist/analysis/tree-builder.d.ts.map +1 -0
  20. package/dist/analysis/tree-builder.js +47 -0
  21. package/dist/analysis/tree-builder.js.map +1 -0
  22. package/dist/cli.d.ts +3 -0
  23. package/dist/cli.d.ts.map +1 -0
  24. package/dist/cli.js +184 -0
  25. package/dist/cli.js.map +1 -0
  26. package/dist/loader/hooks.d.ts +4 -0
  27. package/dist/loader/hooks.d.ts.map +1 -0
  28. package/dist/loader/hooks.js +79 -0
  29. package/dist/loader/hooks.js.map +1 -0
  30. package/dist/loader/register.d.ts +2 -0
  31. package/dist/loader/register.d.ts.map +1 -0
  32. package/dist/loader/register.js +35 -0
  33. package/dist/loader/register.js.map +1 -0
  34. package/dist/report/generator.d.ts +3 -0
  35. package/dist/report/generator.d.ts.map +1 -0
  36. package/dist/report/generator.js +50 -0
  37. package/dist/report/generator.js.map +1 -0
  38. package/dist/report/template.html +146 -0
  39. package/dist/report/ui/cycles-panel.js +80 -0
  40. package/dist/report/ui/filters.js +13 -0
  41. package/dist/report/ui/graph.js +1310 -0
  42. package/dist/report/ui/styles.css +531 -0
  43. package/dist/report/ui/table.js +209 -0
  44. package/dist/types.d.ts +47 -0
  45. package/dist/types.d.ts.map +1 -0
  46. package/dist/types.js +2 -0
  47. package/dist/types.js.map +1 -0
  48. 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
+ ![ESM Imports Analyzer](screenshot.png)
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,3 @@
1
+ import type { ImportRecord, Cycle } from '../types.ts';
2
+ export declare function detectCycles(records: ImportRecord[]): Cycle[];
3
+ //# sourceMappingURL=cycle-detector.d.ts.map
@@ -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,3 @@
1
+ import type { FolderTreeNode } from '../types.ts';
2
+ export declare function buildFolderTree(groupId: string, packageDir: string, moduleURLs: string[]): FolderTreeNode[];
3
+ //# sourceMappingURL=folder-tree.d.ts.map
@@ -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,4 @@
1
+ import type { ImportRecord, Group } from '../types.ts';
2
+ export declare function groupModules(records: ImportRecord[]): Group[];
3
+ export declare function clearPackageJsonCache(): void;
4
+ //# sourceMappingURL=grouper.d.ts.map
@@ -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,3 @@
1
+ import type { ImportRecord, ModuleNode } from '../types.ts';
2
+ export declare function buildTree(records: ImportRecord[]): ModuleNode[];
3
+ //# sourceMappingURL=tree-builder.d.ts.map
@@ -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"}