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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function buildTree(records) {
|
|
2
|
+
if (records.length === 0)
|
|
3
|
+
return [];
|
|
4
|
+
// Index records by resolvedURL — first occurrence wins (subsequent are cached)
|
|
5
|
+
const recordByURL = new Map();
|
|
6
|
+
for (const record of records) {
|
|
7
|
+
if (!recordByURL.has(record.resolvedURL)) {
|
|
8
|
+
recordByURL.set(record.resolvedURL, record);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
// Build nodes
|
|
12
|
+
const nodeByURL = new Map();
|
|
13
|
+
for (const [url, record] of recordByURL) {
|
|
14
|
+
nodeByURL.set(url, {
|
|
15
|
+
resolvedURL: url,
|
|
16
|
+
specifier: record.specifier,
|
|
17
|
+
totalTime: record.totalImportTime ?? 0,
|
|
18
|
+
children: [],
|
|
19
|
+
parentURL: record.parentURL,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
// Link children to parents, collect roots
|
|
23
|
+
const roots = [];
|
|
24
|
+
// Sort by loadStartTime so children are ordered by load order
|
|
25
|
+
const sortedNodes = [...nodeByURL.values()].sort((a, b) => {
|
|
26
|
+
const aRec = recordByURL.get(a.resolvedURL);
|
|
27
|
+
const bRec = recordByURL.get(b.resolvedURL);
|
|
28
|
+
return aRec.importStartTime - bRec.importStartTime;
|
|
29
|
+
});
|
|
30
|
+
for (const node of sortedNodes) {
|
|
31
|
+
if (node.parentURL === null) {
|
|
32
|
+
roots.push(node);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const parent = nodeByURL.get(node.parentURL);
|
|
36
|
+
if (parent) {
|
|
37
|
+
parent.children.push(node);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Parent not found — treat as root
|
|
41
|
+
roots.push(node);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return roots;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=tree-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-builder.js","sourceRoot":"","sources":["../../src/analysis/tree-builder.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,SAAS,CAAC,OAAuB;IAC/C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,+EAA+E;IAC/E,MAAM,WAAW,GAAG,IAAI,GAAG,EAAwB,CAAC;IACpD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;YACzC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,cAAc;IACd,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;IAChD,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QACxC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE;YACjB,WAAW,EAAE,GAAG;YAChB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,SAAS,EAAE,MAAM,CAAC,eAAe,IAAI,CAAC;YACtC,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,MAAM,CAAC,SAAS;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,0CAA0C;IAC1C,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,8DAA8D;IAC9D,MAAM,WAAW,GAAG,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACxD,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAE,CAAC;QAC7C,OAAO,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC7C,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,CAAC;iBAAM,CAAC;gBACN,mCAAmC;gBACnC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { readFileSync, unlinkSync, existsSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join, resolve, dirname } from 'node:path';
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { buildTree } from "./analysis/tree-builder.js";
|
|
9
|
+
import { detectCycles } from "./analysis/cycle-detector.js";
|
|
10
|
+
import { groupModules } from "./analysis/grouper.js";
|
|
11
|
+
import { computeRankedList, computeTotalTime } from "./analysis/timing.js";
|
|
12
|
+
import { generateReport } from "./report/generator.js";
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
function findProjectRoot() {
|
|
15
|
+
let dir = __dirname;
|
|
16
|
+
while (dir !== dirname(dir)) {
|
|
17
|
+
if (existsSync(join(dir, 'package.json')))
|
|
18
|
+
return dir;
|
|
19
|
+
dir = dirname(dir);
|
|
20
|
+
}
|
|
21
|
+
return __dirname;
|
|
22
|
+
}
|
|
23
|
+
function printUsage() {
|
|
24
|
+
console.log(`
|
|
25
|
+
ESM Imports Analyzer
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
esm-imports-analyzer [options] -- <command> [command-args...]
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
--output, -o <path> Output HTML report path (default: ./esm-imports-report.html)
|
|
32
|
+
--help, -h Show help
|
|
33
|
+
--version, -v Show version
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
npx esm-imports-analyzer -- node app.js
|
|
37
|
+
npx esm-imports-analyzer -o report.html -- node server.js
|
|
38
|
+
`);
|
|
39
|
+
}
|
|
40
|
+
function getVersion() {
|
|
41
|
+
try {
|
|
42
|
+
const projectRoot = findProjectRoot();
|
|
43
|
+
const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8'));
|
|
44
|
+
return pkg['version'] ?? 'unknown';
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return 'unknown';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function parseArgs(args) {
|
|
51
|
+
// Check for help/version before separator check
|
|
52
|
+
for (const arg of args) {
|
|
53
|
+
if (arg === '--help' || arg === '-h') {
|
|
54
|
+
printUsage();
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
if (arg === '--version' || arg === '-v') {
|
|
58
|
+
console.log(getVersion());
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const separatorIndex = args.indexOf('--');
|
|
63
|
+
if (separatorIndex === -1) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const ourArgs = args.slice(0, separatorIndex);
|
|
67
|
+
const command = args.slice(separatorIndex + 1);
|
|
68
|
+
if (command.length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
let outputPath = resolve('esm-imports-report.html');
|
|
72
|
+
for (let i = 0; i < ourArgs.length; i++) {
|
|
73
|
+
if (ourArgs[i] === '--output' || ourArgs[i] === '-o') {
|
|
74
|
+
const nextArg = ourArgs[i + 1];
|
|
75
|
+
if (nextArg) {
|
|
76
|
+
outputPath = resolve(nextArg);
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { outputPath, command };
|
|
82
|
+
}
|
|
83
|
+
function fail(message) {
|
|
84
|
+
console.error(message);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
async function main() {
|
|
88
|
+
const args = process.argv.slice(2);
|
|
89
|
+
const parsed = parseArgs(args);
|
|
90
|
+
if (!parsed) {
|
|
91
|
+
console.error('Error: Missing -- separator. Usage: esm-imports-analyzer [options] -- <command>');
|
|
92
|
+
printUsage();
|
|
93
|
+
return fail('');
|
|
94
|
+
}
|
|
95
|
+
const { outputPath, command } = parsed;
|
|
96
|
+
const tempFile = join(tmpdir(), `esm-analyzer-${randomUUID()}.json`);
|
|
97
|
+
// Determine the register script path
|
|
98
|
+
const projectRoot = findProjectRoot();
|
|
99
|
+
const registerPath = __dirname.includes('dist')
|
|
100
|
+
? join(projectRoot, 'dist', 'loader', 'register.js')
|
|
101
|
+
: join(projectRoot, 'src', 'loader', 'register.ts');
|
|
102
|
+
// Prepare NODE_OPTIONS
|
|
103
|
+
const existingNodeOptions = process.env['NODE_OPTIONS'] ?? '';
|
|
104
|
+
const nodeOptions = `--import=${registerPath} ${existingNodeOptions}`.trim();
|
|
105
|
+
const cmd = command[0];
|
|
106
|
+
const cmdArgs = command.slice(1);
|
|
107
|
+
if (!cmd) {
|
|
108
|
+
return fail('Error: No command specified after --');
|
|
109
|
+
}
|
|
110
|
+
const child = spawn(cmd, cmdArgs, {
|
|
111
|
+
stdio: 'inherit',
|
|
112
|
+
env: {
|
|
113
|
+
...process.env,
|
|
114
|
+
NODE_OPTIONS: nodeOptions,
|
|
115
|
+
ESM_ANALYZER_TEMP_FILE: tempFile,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
const exitCode = await new Promise((resolvePromise) => {
|
|
119
|
+
child.on('close', (code) => {
|
|
120
|
+
resolvePromise(code ?? 0);
|
|
121
|
+
});
|
|
122
|
+
child.on('error', (err) => {
|
|
123
|
+
console.error(`Error spawning command: ${err.message}`);
|
|
124
|
+
resolvePromise(1);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
if (exitCode !== 0) {
|
|
128
|
+
console.warn(`\nWarning: Command exited with code ${exitCode}. Generating report from collected data.`);
|
|
129
|
+
}
|
|
130
|
+
// Read collected data
|
|
131
|
+
if (!existsSync(tempFile)) {
|
|
132
|
+
return fail('Error: No import data collected. Is the project using ESM?');
|
|
133
|
+
}
|
|
134
|
+
let rawData;
|
|
135
|
+
try {
|
|
136
|
+
rawData = readFileSync(tempFile, 'utf-8');
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return fail('Error: Could not read import data file.');
|
|
140
|
+
}
|
|
141
|
+
if (!rawData.trim()) {
|
|
142
|
+
return fail('Error: No imports were captured. Is the project using ESM?');
|
|
143
|
+
}
|
|
144
|
+
let records;
|
|
145
|
+
try {
|
|
146
|
+
records = JSON.parse(rawData);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return fail('Error: Import data file contains invalid JSON.');
|
|
150
|
+
}
|
|
151
|
+
// Clean up temp file
|
|
152
|
+
try {
|
|
153
|
+
unlinkSync(tempFile);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Best effort cleanup
|
|
157
|
+
}
|
|
158
|
+
// Run analysis
|
|
159
|
+
const tree = buildTree(records);
|
|
160
|
+
const cycles = detectCycles(records);
|
|
161
|
+
const groups = groupModules(records);
|
|
162
|
+
const rankedList = computeRankedList(records);
|
|
163
|
+
const totalTime = computeTotalTime(records);
|
|
164
|
+
const reportData = {
|
|
165
|
+
metadata: {
|
|
166
|
+
command: command.join(' '),
|
|
167
|
+
timestamp: new Date().toISOString(),
|
|
168
|
+
nodeVersion: process.version,
|
|
169
|
+
totalModules: rankedList.length,
|
|
170
|
+
totalTime,
|
|
171
|
+
},
|
|
172
|
+
modules: records,
|
|
173
|
+
tree,
|
|
174
|
+
groups,
|
|
175
|
+
cycles,
|
|
176
|
+
};
|
|
177
|
+
generateReport(reportData, outputPath);
|
|
178
|
+
console.log(`\nReport generated: ${outputPath}`);
|
|
179
|
+
}
|
|
180
|
+
main().catch((err) => {
|
|
181
|
+
console.error('Fatal error:', err);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
});
|
|
184
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/loader/hooks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,oBAAoB,EAAuC,MAAM,aAAa,CAAC;AAmB7F,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,oBAAoB,CAyE1G"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
const SOURCE_MAP_RE = /(\n\/\/[#@] sourceMappingURL=[^\n]*\s*)$/;
|
|
4
|
+
function injectCallback(source, url) {
|
|
5
|
+
const injection = `\n;globalThis.__esm_analyzer_import_done__(${JSON.stringify(url)});\n`;
|
|
6
|
+
if (SOURCE_MAP_RE.test(source)) {
|
|
7
|
+
return source.replace(SOURCE_MAP_RE, injection + '$1');
|
|
8
|
+
}
|
|
9
|
+
return source + injection;
|
|
10
|
+
}
|
|
11
|
+
export function createHooks(records, evalStarts) {
|
|
12
|
+
const loadedURLs = new Set();
|
|
13
|
+
const pendingQueue = [];
|
|
14
|
+
return {
|
|
15
|
+
resolve(specifier, context, nextResolve) {
|
|
16
|
+
const importStartTime = performance.now();
|
|
17
|
+
const result = nextResolve(specifier, context);
|
|
18
|
+
if (loadedURLs.has(result.url)) {
|
|
19
|
+
// Already loaded (cached import or circular back-edge).
|
|
20
|
+
records.push({
|
|
21
|
+
specifier,
|
|
22
|
+
resolvedURL: result.url,
|
|
23
|
+
parentURL: context.parentURL ?? null,
|
|
24
|
+
importStartTime,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
pendingQueue.push({
|
|
29
|
+
specifier,
|
|
30
|
+
resolvedURL: result.url,
|
|
31
|
+
parentURL: context.parentURL ?? null,
|
|
32
|
+
importStartTime,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
},
|
|
37
|
+
load(url, context, nextLoad) {
|
|
38
|
+
const result = nextLoad(url, context);
|
|
39
|
+
loadedURLs.add(url);
|
|
40
|
+
// Find the matching pending resolve for this URL
|
|
41
|
+
const idx = pendingQueue.findIndex(p => p.resolvedURL === url);
|
|
42
|
+
if (idx !== -1) {
|
|
43
|
+
const pending = pendingQueue[idx];
|
|
44
|
+
pendingQueue.splice(idx, 1);
|
|
45
|
+
evalStarts.set(url, pending.importStartTime);
|
|
46
|
+
records.push({
|
|
47
|
+
specifier: pending.specifier,
|
|
48
|
+
resolvedURL: url,
|
|
49
|
+
parentURL: pending.parentURL,
|
|
50
|
+
importStartTime: pending.importStartTime,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Inject eval callback into JS module sources
|
|
54
|
+
// format is 'module' for ESM, 'commonjs' for CJS via import(), undefined for CJS via require()
|
|
55
|
+
if (result.format !== 'json' && result.format !== 'wasm') {
|
|
56
|
+
let source = null;
|
|
57
|
+
if (result.source != null) {
|
|
58
|
+
source = typeof result.source === 'string'
|
|
59
|
+
? result.source
|
|
60
|
+
: new TextDecoder().decode(result.source);
|
|
61
|
+
}
|
|
62
|
+
else if (url.startsWith('file://')) {
|
|
63
|
+
// CJS modules loaded via import() may have null source — read from disk
|
|
64
|
+
try {
|
|
65
|
+
source = readFileSync(fileURLToPath(url), 'utf-8');
|
|
66
|
+
}
|
|
67
|
+
catch { }
|
|
68
|
+
}
|
|
69
|
+
if (source !== null) {
|
|
70
|
+
return { ...result, source: injectCallback(source, url) };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Non-JS module (JSON, WASM, builtin) — can't measure eval time
|
|
74
|
+
evalStarts.delete(url);
|
|
75
|
+
return result;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/loader/hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/loader/register.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import module from 'node:module';
|
|
2
|
+
import { writeFileSync } from 'node:fs';
|
|
3
|
+
import { createHooks } from "./hooks.js";
|
|
4
|
+
const records = [];
|
|
5
|
+
const evalStarts = new Map();
|
|
6
|
+
const evalTimes = new Map();
|
|
7
|
+
globalThis.__esm_analyzer_import_done__ = (url) => {
|
|
8
|
+
const start = evalStarts.get(url);
|
|
9
|
+
if (start !== undefined) {
|
|
10
|
+
evalTimes.set(url, performance.now() - start);
|
|
11
|
+
evalStarts.delete(url);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const hooks = createHooks(records, evalStarts);
|
|
15
|
+
module.registerHooks(hooks);
|
|
16
|
+
const tempFile = process.env['ESM_ANALYZER_TEMP_FILE'];
|
|
17
|
+
function flushData() {
|
|
18
|
+
if (tempFile && records.length > 0) {
|
|
19
|
+
for (const record of records) {
|
|
20
|
+
const t = evalTimes.get(record.resolvedURL);
|
|
21
|
+
if (t !== undefined) {
|
|
22
|
+
record.totalImportTime = t;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
writeFileSync(tempFile, JSON.stringify(records));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Best effort — if we can't write, data is lost
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
process.on('beforeExit', flushData);
|
|
34
|
+
process.on('exit', flushData);
|
|
35
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/loader/register.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,OAAO,GAAmB,EAAE,CAAC;AACnC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;AAC7C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;AAE3C,UAAkB,CAAC,4BAA4B,GAAG,CAAC,GAAW,EAAE,EAAE;IACjE,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;QAC9C,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;AAC/C,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;AAE5B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AAEvD,SAAS,SAAS;IAChB,IAAI,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;gBACpB,MAAM,CAAC,eAAe,GAAG,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,gDAAgD;QAClD,CAAC;IACH,CAAC;AACH,CAAC;AAED,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AACpC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/report/generator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA6B9C,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CA2BzE"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
function findProjectRoot() {
|
|
6
|
+
let dir = __dirname;
|
|
7
|
+
while (dir !== dirname(dir)) {
|
|
8
|
+
try {
|
|
9
|
+
readFileSync(join(dir, 'package.json'), 'utf-8');
|
|
10
|
+
return dir;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
dir = dirname(dir);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return __dirname;
|
|
17
|
+
}
|
|
18
|
+
function readFileContent(filePath) {
|
|
19
|
+
return readFileSync(filePath, 'utf-8');
|
|
20
|
+
}
|
|
21
|
+
// Use split/join instead of String.replace to avoid $-substitution issues
|
|
22
|
+
// in replacement strings (e.g. minified JS containing $1, $&, etc.)
|
|
23
|
+
function templateReplace(html, placeholder, value) {
|
|
24
|
+
return html.split(placeholder).join(value);
|
|
25
|
+
}
|
|
26
|
+
const isDistBuild = __dirname.includes('dist');
|
|
27
|
+
export function generateReport(data, outputPath) {
|
|
28
|
+
const projectRoot = findProjectRoot();
|
|
29
|
+
const reportDir = isDistBuild
|
|
30
|
+
? join(projectRoot, 'dist', 'report')
|
|
31
|
+
: join(projectRoot, 'src', 'report');
|
|
32
|
+
const uiDir = join(reportDir, 'ui');
|
|
33
|
+
// Read template and UI files
|
|
34
|
+
const template = readFileContent(join(reportDir, 'template.html'));
|
|
35
|
+
const styles = readFileContent(join(uiDir, 'styles.css'));
|
|
36
|
+
const graphJS = readFileContent(join(uiDir, 'graph.js'));
|
|
37
|
+
const tableJS = readFileContent(join(uiDir, 'table.js'));
|
|
38
|
+
const cyclesPanelJS = readFileContent(join(uiDir, 'cycles-panel.js'));
|
|
39
|
+
const filtersJS = readFileContent(join(uiDir, 'filters.js'));
|
|
40
|
+
// Assemble HTML
|
|
41
|
+
let html = template;
|
|
42
|
+
html = templateReplace(html, '{{STYLES}}', styles);
|
|
43
|
+
html = templateReplace(html, '{{DATA}}', JSON.stringify(data));
|
|
44
|
+
html = templateReplace(html, '{{GRAPH_JS}}', graphJS);
|
|
45
|
+
html = templateReplace(html, '{{TABLE_JS}}', tableJS);
|
|
46
|
+
html = templateReplace(html, '{{CYCLES_PANEL_JS}}', cyclesPanelJS);
|
|
47
|
+
html = templateReplace(html, '{{FILTERS_JS}}', filtersJS);
|
|
48
|
+
writeFileSync(outputPath, html);
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/report/generator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,SAAS,eAAe;IACtB,IAAI,GAAG,GAAG,SAAS,CAAC;IACpB,OAAO,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC;YACjD,OAAO,GAAG,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,eAAe,CAAC,QAAgB;IACvC,OAAO,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC;AAED,0EAA0E;AAC1E,oEAAoE;AACpE,SAAS,eAAe,CAAC,IAAY,EAAE,WAAmB,EAAE,KAAa;IACvE,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,WAAW,GAAG,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAE/C,MAAM,UAAU,cAAc,CAAC,IAAgB,EAAE,UAAkB;IACjE,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;IAEtC,MAAM,SAAS,GAAG,WAAW;QAC3B,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC;QACrC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAEvC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAEpC,6BAA6B;IAC7B,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC;IACnE,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;IACzD,MAAM,aAAa,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACtE,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAE7D,gBAAgB;IAChB,IAAI,IAAI,GAAG,QAAQ,CAAC;IACpB,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;IACnD,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/D,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;IACtD,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;IACtD,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,qBAAqB,EAAE,aAAa,CAAC,CAAC;IACnE,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,SAAS,CAAC,CAAC;IAE1D,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>ESM Imports Analyzer</title>
|
|
7
|
+
<style>
|
|
8
|
+
{{STYLES}}
|
|
9
|
+
</style>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div class="header">
|
|
13
|
+
<h1>ESM Imports Analyzer</h1>
|
|
14
|
+
<div class="header-meta">
|
|
15
|
+
<span id="meta-command"></span> ·
|
|
16
|
+
<span id="meta-modules"></span> modules
|
|
17
|
+
</div>
|
|
18
|
+
<div class="header-controls">
|
|
19
|
+
<div class="filter-group">
|
|
20
|
+
<input type="text" class="search-input" id="search-input" placeholder="Search modules...">
|
|
21
|
+
</div>
|
|
22
|
+
<div class="filter-group">
|
|
23
|
+
<button class="relayout-btn" id="expand-all-btn">Expand all</button>
|
|
24
|
+
<button class="relayout-btn" id="collapse-all-btn">Collapse all</button>
|
|
25
|
+
<button class="relayout-btn" id="relayout-btn">Re-layout</button>
|
|
26
|
+
<label><input type="checkbox" id="auto-relayout" checked> Auto re-layout</label>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="main-container">
|
|
32
|
+
<div class="cycles-panel" id="cycles-panel">
|
|
33
|
+
<div class="cycles-header">
|
|
34
|
+
<h2>Circular Dependencies</h2>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="cycles-list" id="cycles-list"></div>
|
|
37
|
+
<button class="clear-highlight-btn" id="clear-highlight-btn">Clear highlight</button>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="center-area">
|
|
41
|
+
<div class="graph-container">
|
|
42
|
+
<div id="cy"></div>
|
|
43
|
+
<div class="graph-tooltip" id="graph-tooltip"></div>
|
|
44
|
+
<div class="layout-overlay" id="layout-overlay">
|
|
45
|
+
<div class="layout-overlay-content">
|
|
46
|
+
<div class="layout-spinner"></div>
|
|
47
|
+
<div>Computing layout...</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="resize-handle" id="resize-handle"></div>
|
|
52
|
+
<div class="table-container" id="table-container">
|
|
53
|
+
<div class="table-title-row">
|
|
54
|
+
<span class="table-title">Slowest modules</span>
|
|
55
|
+
<span class="table-count-control">Count: <input type="number" id="slowest-count" value="20" min="1" max="999"></span>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="table-header-row">
|
|
58
|
+
<span class="col-module" data-sort="name">Module <span class="sort-arrow"></span></span>
|
|
59
|
+
<span class="col-time" data-sort="time">Import time <span class="header-info-icon" title="Total time to fully import this module, including resolution, loading, parsing, dependencies, and top-level code execution">ⓘ</span> <span class="sort-arrow">▼</span></span>
|
|
60
|
+
<span class="col-imports" data-sort="imports">Imports <span class="header-info-icon" title="Total number of modules imported by this module (recursively)">ⓘ</span> <span class="sort-arrow"></span></span>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="table-body" id="table-body"></div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<script type="application/json" id="import-data">
|
|
68
|
+
{{DATA}}
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<script src="https://unpkg.com/cytoscape@3.31.0/dist/cytoscape.min.js"></script>
|
|
72
|
+
|
|
73
|
+
<script>
|
|
74
|
+
{{GRAPH_JS}}
|
|
75
|
+
</script>
|
|
76
|
+
<script>
|
|
77
|
+
{{TABLE_JS}}
|
|
78
|
+
</script>
|
|
79
|
+
<script>
|
|
80
|
+
{{CYCLES_PANEL_JS}}
|
|
81
|
+
</script>
|
|
82
|
+
<script>
|
|
83
|
+
{{FILTERS_JS}}
|
|
84
|
+
</script>
|
|
85
|
+
<script>
|
|
86
|
+
(function () {
|
|
87
|
+
var raw = document.getElementById('import-data').textContent;
|
|
88
|
+
var data = JSON.parse(raw);
|
|
89
|
+
|
|
90
|
+
// Set metadata
|
|
91
|
+
document.getElementById('meta-command').textContent = data.metadata.command;
|
|
92
|
+
document.getElementById('meta-modules').textContent = data.metadata.totalModules;
|
|
93
|
+
|
|
94
|
+
// Init components
|
|
95
|
+
var cy = initGraph(data);
|
|
96
|
+
var tableApi = initTable(data, cy);
|
|
97
|
+
initCyclesPanel(data, cy);
|
|
98
|
+
initFilters(cy, tableApi);
|
|
99
|
+
|
|
100
|
+
// Expand/Collapse all buttons
|
|
101
|
+
document.getElementById('expand-all-btn').addEventListener('click', function () {
|
|
102
|
+
expandAll(cy);
|
|
103
|
+
maybeRelayoutWithCallback(cy, function () { applySelectionHighlight(cy); });
|
|
104
|
+
});
|
|
105
|
+
document.getElementById('collapse-all-btn').addEventListener('click', function () {
|
|
106
|
+
collapseAll(cy);
|
|
107
|
+
maybeRelayoutWithCallback(cy, function () { applySelectionHighlight(cy); });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Re-layout button
|
|
111
|
+
document.getElementById('relayout-btn').addEventListener('click', function () {
|
|
112
|
+
runLayout(cy);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Auto re-layout checkbox
|
|
116
|
+
var autoRelayoutCheckbox = document.getElementById('auto-relayout');
|
|
117
|
+
autoRelayoutCheckbox.addEventListener('change', function () {
|
|
118
|
+
autoRelayout = autoRelayoutCheckbox.checked;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Resize handle
|
|
122
|
+
var resizeHandle = document.getElementById('resize-handle');
|
|
123
|
+
var tableContainer = document.getElementById('table-container');
|
|
124
|
+
var isResizing = false;
|
|
125
|
+
|
|
126
|
+
resizeHandle.addEventListener('mousedown', function (e) {
|
|
127
|
+
isResizing = true;
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
document.addEventListener('mousemove', function (e) {
|
|
132
|
+
if (!isResizing) return;
|
|
133
|
+
var containerRect = document.querySelector('.center-area').getBoundingClientRect();
|
|
134
|
+
var newHeight = containerRect.bottom - e.clientY;
|
|
135
|
+
newHeight = Math.max(100, Math.min(newHeight, containerRect.height - 200));
|
|
136
|
+
tableContainer.style.height = newHeight + 'px';
|
|
137
|
+
cy.resize();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
document.addEventListener('mouseup', function () {
|
|
141
|
+
isResizing = false;
|
|
142
|
+
});
|
|
143
|
+
})();
|
|
144
|
+
</script>
|
|
145
|
+
</body>
|
|
146
|
+
</html>
|