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
@@ -0,0 +1,209 @@
1
+ /* global document */
2
+
3
+ function initTable(data, cy) {
4
+ var tableBody = document.getElementById('table-body');
5
+ var countInput = document.getElementById('slowest-count');
6
+
7
+ // Build URL -> ModuleNode index from the full tree
8
+ var nodeByURL = {};
9
+ function indexTree(nodes) {
10
+ for (var i = 0; i < nodes.length; i++) {
11
+ nodeByURL[nodes[i].resolvedURL] = nodes[i];
12
+ indexTree(nodes[i].children);
13
+ }
14
+ }
15
+ indexTree(data.tree);
16
+
17
+ // Build timing lookup (first occurrence per URL)
18
+ var timingByURL = {};
19
+ for (var i = 0; i < data.modules.length; i++) {
20
+ var m = data.modules[i];
21
+ if (!timingByURL[m.resolvedURL]) {
22
+ timingByURL[m.resolvedURL] = {
23
+ totalImportTime: m.totalImportTime || 0,
24
+ };
25
+ }
26
+ }
27
+
28
+ // Collect all unique modules that have tree nodes
29
+ var allModules = [];
30
+ var seen = {};
31
+ for (var i = 0; i < data.modules.length; i++) {
32
+ var url = data.modules[i].resolvedURL;
33
+ if (!seen[url] && nodeByURL[url]) {
34
+ seen[url] = true;
35
+ allModules.push(nodeByURL[url]);
36
+ }
37
+ }
38
+
39
+ function getMetric(node, column) {
40
+ var t = timingByURL[node.resolvedURL];
41
+ if (column === 'time') return t ? t.totalImportTime : node.totalTime;
42
+ if (column === 'imports') return countAllChildren(node);
43
+ return 0;
44
+ }
45
+
46
+ function buildRoots(count, sortColumn) {
47
+ var sorted = allModules.slice().sort(function (a, b) {
48
+ return getMetric(b, sortColumn) - getMetric(a, sortColumn);
49
+ });
50
+ return sorted.slice(0, count);
51
+ }
52
+
53
+ var currentSort = { column: 'time', direction: 'desc' };
54
+
55
+ function getDisplayPath(url) {
56
+ if (url.startsWith('file://')) return url.slice(7);
57
+ return url;
58
+ }
59
+
60
+ function countAllChildren(node) {
61
+ var count = node.children.length;
62
+ for (var i = 0; i < node.children.length; i++) {
63
+ count += countAllChildren(node.children[i]);
64
+ }
65
+ return count;
66
+ }
67
+
68
+ function renderRow(node, depth, parentElement) {
69
+ var row = document.createElement('div');
70
+ row.className = 'table-row';
71
+ row.dataset.url = node.resolvedURL;
72
+ row.dataset.depth = depth;
73
+
74
+ var hasChildren = node.children.length > 0;
75
+ var indent = depth * 20;
76
+ var childCount = countAllChildren(node);
77
+ var t = timingByURL[node.resolvedURL] || { totalImportTime: node.totalTime };
78
+ var timeDisplay = t.totalImportTime ? t.totalImportTime.toFixed(2) + ' ms' : '\u2014';
79
+
80
+ row.innerHTML =
81
+ '<div class="module-name" style="padding-left: ' + indent + 'px">' +
82
+ '<span class="chevron' + (hasChildren ? '' : ' empty') + '">' + (hasChildren ? '\u25B6' : '') + '</span>' +
83
+ '<span class="module-label" title="' + escapeAttr(node.resolvedURL) + '">' + escapeHtml(depth === 0 ? getDisplayPath(node.resolvedURL) : node.specifier) + '</span>' +
84
+ '</div>' +
85
+ '<div class="time-value">' + timeDisplay + '</div>' +
86
+ '<div class="imports-count">' + childCount + '</div>';
87
+
88
+ parentElement.appendChild(row);
89
+
90
+ // Click to zoom in graph
91
+ row.addEventListener('click', function (e) {
92
+ if (e.target.classList.contains('chevron')) return;
93
+ if (cy) {
94
+ focusOnNode(cy, node.resolvedURL);
95
+ }
96
+ // Highlight this row
97
+ var rows = document.querySelectorAll('.table-row');
98
+ for (var j = 0; j < rows.length; j++) {
99
+ rows[j].classList.remove('highlighted');
100
+ }
101
+ row.classList.add('highlighted');
102
+ });
103
+
104
+ // Expand/collapse children
105
+ if (hasChildren) {
106
+ var expanded = false;
107
+ var childContainer = document.createElement('div');
108
+ childContainer.className = 'child-container';
109
+ childContainer.style.display = 'none';
110
+ parentElement.appendChild(childContainer);
111
+
112
+ var chevron = row.querySelector('.chevron');
113
+ chevron.addEventListener('click', function () {
114
+ expanded = !expanded;
115
+ chevron.classList.toggle('expanded', expanded);
116
+ childContainer.style.display = expanded ? 'block' : 'none';
117
+
118
+ if (expanded && childContainer.children.length === 0) {
119
+ var sortedChildren = sortNodes(node.children, currentSort);
120
+ for (var k = 0; k < sortedChildren.length; k++) {
121
+ renderRow(sortedChildren[k], depth + 1, childContainer);
122
+ }
123
+ }
124
+ });
125
+ }
126
+ }
127
+
128
+ function sortNodes(nodes, sort) {
129
+ var sorted = nodes.slice();
130
+ sorted.sort(function (a, b) {
131
+ var cmp = 0;
132
+ if (sort.column === 'name') {
133
+ cmp = a.specifier.localeCompare(b.specifier);
134
+ } else {
135
+ cmp = getMetric(a, sort.column) - getMetric(b, sort.column);
136
+ }
137
+ return sort.direction === 'desc' ? -cmp : cmp;
138
+ });
139
+ return sorted;
140
+ }
141
+
142
+ function renderTable() {
143
+ tableBody.innerHTML = '';
144
+ var count = parseInt(countInput.value, 10) || 20;
145
+ var roots = sortNodes(buildRoots(count, currentSort.column), currentSort);
146
+ for (var i = 0; i < roots.length; i++) {
147
+ renderRow(roots[i], 0, tableBody);
148
+ }
149
+ }
150
+
151
+ // Count input change
152
+ countInput.addEventListener('input', function () {
153
+ renderTable();
154
+ });
155
+
156
+ // Column sorting
157
+ document.querySelectorAll('.table-header-row span[data-sort]').forEach(function (header) {
158
+ header.addEventListener('click', function () {
159
+ var col = header.dataset.sort;
160
+ if (currentSort.column === col) {
161
+ currentSort.direction = currentSort.direction === 'desc' ? 'asc' : 'desc';
162
+ } else {
163
+ currentSort.column = col;
164
+ currentSort.direction = 'desc';
165
+ }
166
+ // Update sort arrows
167
+ document.querySelectorAll('.sort-arrow').forEach(function (arrow) {
168
+ arrow.textContent = '';
169
+ });
170
+ var arrow = header.querySelector('.sort-arrow');
171
+ if (arrow) {
172
+ arrow.textContent = currentSort.direction === 'desc' ? ' \u25BC' : ' \u25B2';
173
+ }
174
+ renderTable();
175
+ });
176
+ });
177
+
178
+ renderTable();
179
+
180
+ function escapeHtml(str) {
181
+ var div = document.createElement('div');
182
+ div.textContent = str;
183
+ return div.innerHTML;
184
+ }
185
+
186
+ function escapeAttr(str) {
187
+ return str.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
188
+ }
189
+
190
+ return {
191
+ filter: function (query) {
192
+ // Only filter top-level rows (depth 0), not expanded children
193
+ var rows = tableBody.querySelectorAll('.table-row[data-depth="0"]');
194
+ var lowerQuery = query.toLowerCase();
195
+ for (var i = 0; i < rows.length; i++) {
196
+ var url = (rows[i].dataset.url || '').toLowerCase();
197
+ var text = rows[i].textContent.toLowerCase();
198
+ var match = !query || url.indexOf(lowerQuery) !== -1 || text.indexOf(lowerQuery) !== -1;
199
+ // Hide/show the row and its sibling child-container
200
+ rows[i].style.display = match ? '' : 'none';
201
+ var next = rows[i].nextElementSibling;
202
+ if (next && next.classList.contains('child-container')) {
203
+ next.style.display = match ? next.style.display : 'none';
204
+ }
205
+ }
206
+ },
207
+ rerender: renderTable,
208
+ };
209
+ }
@@ -0,0 +1,47 @@
1
+ export interface ImportRecord {
2
+ specifier: string;
3
+ resolvedURL: string;
4
+ parentURL: string | null;
5
+ importStartTime: number;
6
+ totalImportTime?: number;
7
+ }
8
+ export interface ModuleNode {
9
+ resolvedURL: string;
10
+ specifier: string;
11
+ totalTime: number;
12
+ children: ModuleNode[];
13
+ parentURL: string | null;
14
+ }
15
+ export interface Cycle {
16
+ modules: string[];
17
+ length: number;
18
+ }
19
+ export interface FolderTreeNode {
20
+ id: string;
21
+ label: string;
22
+ type: 'folder' | 'file';
23
+ moduleURL?: string;
24
+ children: FolderTreeNode[];
25
+ }
26
+ export interface Group {
27
+ id: string;
28
+ label: string;
29
+ packageJsonPath: string;
30
+ modules: string[];
31
+ isNodeModules: boolean;
32
+ folderTree?: FolderTreeNode[];
33
+ }
34
+ export interface ReportData {
35
+ metadata: {
36
+ command: string;
37
+ timestamp: string;
38
+ nodeVersion: string;
39
+ totalModules: number;
40
+ totalTime: number;
41
+ };
42
+ modules: ImportRecord[];
43
+ tree: ModuleNode[];
44
+ groups: Group[];
45
+ cycles: Cycle[];
46
+ }
47
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE;QACR,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "esm-imports-analyzer",
3
+ "version": "0.1.0",
4
+ "description": "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.",
5
+ "keywords": [
6
+ "node",
7
+ "esm",
8
+ "imports",
9
+ "analyzer",
10
+ "optimization"
11
+ ],
12
+ "author": "Patricio Palladino <email@patriciopalladino.com>",
13
+ "license": "MIT",
14
+ "type": "module",
15
+ "bin": {
16
+ "esm-imports-analyzer": "dist/cli.js"
17
+ },
18
+ "types": "dist/cli.d.ts",
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "devDependencies": {
23
+ "@types/node": "^25.5.0",
24
+ "typescript": "^6.0.2"
25
+ },
26
+ "scripts": {
27
+ "typecheck": "tsc --noEmit",
28
+ "build": "rm -rf dist && tsc && cp -r src/report/ui dist/report/ui && cp src/report/template.html dist/report/template.html",
29
+ "test": "node --test test/**/*.test.ts",
30
+ "test:unit": "node --test test/unit/**/*.test.ts",
31
+ "test:integration": "node --test test/integration/**/*.test.ts"
32
+ }
33
+ }