@typeslayer/analyze-trace 0.0.0 â 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 +85 -0
- package/bin/typeslayer-analyze-trace.mjs +42 -0
- package/dist/index.d.ts +4307 -0
- package/dist/index.js +669 -0
- package/dist/index.js.map +1 -0
- package/package.json +33 -2
- package/src/analyze-trace.ts +99 -0
- package/src/constants.ts +2 -0
- package/src/depth-limits.ts +106 -0
- package/src/get-duplicate-node-modules.ts +46 -0
- package/src/get-hotspots.ts +245 -0
- package/src/index.ts +4 -0
- package/src/node-module-paths.ts +44 -0
- package/src/spans.ts +137 -0
- package/src/utils.ts +185 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @typeslayer/analyze-trace
|
|
2
|
+
|
|
3
|
+
Analyze TypeScript compiler trace events to identify performance bottlenecks and compilation hot spots.
|
|
4
|
+
|
|
5
|
+
Initially, this started as a full rewrite of [@typescript/analyze-trace](https://github.com/microsoft/typescript-analyze-trace)*. That rewrite was successful, new features were added during the development of [TypeSlayer](https://github.com/dimitropoulos/typeslayer).
|
|
6
|
+
|
|
7
|
+
<sub>
|
|
8
|
+
> \* why did it need a full rewrite? it's been unmaintained for many years and also I ([dimitropoulos](https://github.com/dimitropoulos)) wanted to know every detail of how it works. That ended up being very useful because there were some things that are exceedingly interesting (like some of the instantaneous events for limits being hit) that the original tool ([intentionally](https://github.com/microsoft/typescript-analyze-trace/issues/1), in some cases) completely ignores things that are actually quite fundamental to the goal of TypeSlayer
|
|
9
|
+
> also the original was intended as a end-of-the-pipeline CLI tool (giving nice human readable format, but only experimental JSON support) and the rewrite is though of as a middle-of-the-pipeline tool (1st-class JSON support).
|
|
10
|
+
</sub>
|
|
11
|
+
|
|
12
|
+
## CLI Usage
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
tsc --generateTrace ./trace-json-path
|
|
16
|
+
npx @typeslayer/analyze-trace trace-json-path
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Examples
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Analyze a trace file
|
|
23
|
+
typeslayer-analyze-trace ./trace.json
|
|
24
|
+
|
|
25
|
+
# With custom output
|
|
26
|
+
typeslayer-analyze-trace ./dist/trace.json > analysis.json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Programmatic Usage
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { analyzeTrace } from '@typeslayer/analyze-trace';
|
|
33
|
+
import { readFileSync } from 'fs';
|
|
34
|
+
|
|
35
|
+
// Read and parse trace
|
|
36
|
+
const traceJson = JSON.parse(readFileSync('trace.json', 'utf-8'));
|
|
37
|
+
|
|
38
|
+
// Analyze
|
|
39
|
+
const result = analyzeTrace(traceJson);
|
|
40
|
+
|
|
41
|
+
console.log('Hot spots:', result.hotSpots);
|
|
42
|
+
console.log('Depth limits:', result.depthLimits);
|
|
43
|
+
console.log('Duplicate packages:', result.duplicatePackages);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Analysis Output
|
|
47
|
+
|
|
48
|
+
The analyzer provides:
|
|
49
|
+
|
|
50
|
+
### Hot Spot Detection
|
|
51
|
+
|
|
52
|
+
This is a sliding scale that looks at the events that consumed the most time during compilation. It's not a given that because something is hot it's necessarily _bad_. But, the thinking is, if you see something that's much more intensive than something else, it's worth flagging. So. There you go.
|
|
53
|
+
|
|
54
|
+
### Duplicate Package Detection
|
|
55
|
+
|
|
56
|
+
Package versions appearing in multiple locations from the same package. This is one of those things that people often have no idea is going on, but in reality most projects have this problem. Well. "problem" might be a strong word. Similar to [Hot Spot Detection](#hot-spot-detection), you'll have to use your judgement here to decide whether it's a big deal or not. That's largely going to depend on the size/scale/usage of the thing that's duplicated.
|
|
57
|
+
|
|
58
|
+
### Unterminated Events
|
|
59
|
+
|
|
60
|
+
In a perfect world, all events created by TypeScript's trace machinery should be terminated. What's that mean? Think of it like a missing JSX closing tag. The thinking here is that if that ever happens, it's guaranteed to be a symptom of something else being wrong - so the tool flags it.
|
|
61
|
+
|
|
62
|
+
### Depth Limits (unique to `@typeslayer/analyze-trace`)
|
|
63
|
+
|
|
64
|
+
Type-level limits that were hit during the type checking, including:
|
|
65
|
+
|
|
66
|
+
- `checkCrossProductUnion_DepthLimit`: triggers `TS(2590) Expression produces a union type that is too complex to represent.`
|
|
67
|
+
- `checkTypeRelatedTo_DepthLimit`: triggers `TS(2859) Excessive complexity comparing types '{0}' and '{1}'.` or `TS(2321) Excessive stack depth comparing types '{0}' and '{1}'.`
|
|
68
|
+
- `getTypeAtFlowNode_DepthLimit`: triggers `TS(2563) The containing function or module body is too large for control flow analysis.`
|
|
69
|
+
- `instantiateType_DepthLimit`: triggers `TS(2589) Type instantiation is excessively deep and possibly infinite.`
|
|
70
|
+
- `recursiveTypeRelatedTo_DepthLimit`: This is not currently considered a hard error by the compiler and therefore
|
|
71
|
+
does not report to the user (unless you're a [TypeSlayer](https://github.com/dimitropoulos/typeslayer) user ð).
|
|
72
|
+
- `removeSubtypes_DepthLimit`: triggers `TS(2590) Expression produces a union type that is too complex to represent.`
|
|
73
|
+
- `traceUnionsOrIntersectionsTooLarge_DepthLimit`: This is not currently considered a hard error by the compiler and therefore
|
|
74
|
+
does not report to the user (unless you're a [TypeSlayer](https://github.com/dimitropoulos/typeslayer) user ð).
|
|
75
|
+
- `typeRelatedToDiscriminatedType_DepthLimit`: This is not currently considered a hard error by the compiler and therefore
|
|
76
|
+
does not report to the user (unless you're a [TypeSlayer](https://github.com/dimitropoulos/typeslayer) user ð).
|
|
77
|
+
|
|
78
|
+
### Type Graph
|
|
79
|
+
|
|
80
|
+
> [!NOTE]
|
|
81
|
+
> As fate would have it, when [TypeSlayer](https://github.com/dimitropoulos/typeslayer) moved from Node.js to Rust to be a Tauri app, this entire package was _again_ rewritten in Rust. Since this version was fully up-and-running first, and the original has some issues, I decided to just not delete this and publish it in case others stuck in the Node ecosystem (ðŠĶ) find it useful.
|
|
82
|
+
>
|
|
83
|
+
> This particular feature is only in the Rust version. If you'd like a wasm-build of it or something lemme know.
|
|
84
|
+
|
|
85
|
+
This is the thing that powers the [TypeSlayer](https://github.com/dimitropoulos/typeslayer) "Type Graph". It basically takes the `types.json` output and combines it with the information in the `trace.json` to create a list of relations (of various sorts) between all the types in your project. It also compiles stats for records like "biggest union" and "most commonly included in an intersection" and literally over a doze more like that.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import process, { exit } from "node:process";
|
|
6
|
+
import { ANALYZE_TRACE_FILENAME, analyzeTrace, defaultOptions } from "../dist/index.js";
|
|
7
|
+
|
|
8
|
+
const { argv } = process;
|
|
9
|
+
|
|
10
|
+
const traceDirArg = argv[2];
|
|
11
|
+
|
|
12
|
+
if (!traceDirArg) {
|
|
13
|
+
console.error("Gotta give a trace directory, brah.");
|
|
14
|
+
exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const traceDir = resolve(traceDirArg);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const stat = statSync(traceDir);
|
|
21
|
+
if (!stat.isDirectory()) {
|
|
22
|
+
console.error(`Trace directory "${traceDir}" is not a directory.`);
|
|
23
|
+
exit(1);
|
|
24
|
+
}
|
|
25
|
+
} catch (err) {
|
|
26
|
+
if (err.code === "ENOENT") {
|
|
27
|
+
console.error(`Trace directory "${traceDir}" does not exist.`);
|
|
28
|
+
} else {
|
|
29
|
+
console.error(
|
|
30
|
+
`Error checking trace directory "${traceDir}": ${err.message}`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log({ traceDir });
|
|
37
|
+
|
|
38
|
+
analyzeTrace({ traceDir, options: defaultOptions }).then((result) => {
|
|
39
|
+
const destination = resolve(traceDir, ANALYZE_TRACE_FILENAME);
|
|
40
|
+
writeFileSync(destination, JSON.stringify(result, null, 2), "utf-8");
|
|
41
|
+
console.log("Analysis result:", result);
|
|
42
|
+
});
|