@yetanotheraryan/coldstart 1.0.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 ADDED
@@ -0,0 +1,440 @@
1
+ # coldstart
2
+
3
+ [![npm version](https://img.shields.io/npm/v/coldstart.svg)](https://www.npmjs.com/package/coldstart)
4
+ [![npm downloads](https://img.shields.io/npm/dm/coldstart.svg)](https://www.npmjs.com/package/coldstart)
5
+ [![Node.js >=18](https://img.shields.io/badge/node-%3E%3D18-339933.svg)](https://nodejs.org/)
6
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
7
+
8
+ Profile Node.js startup one module load at a time.
9
+
10
+ `coldstart` is a zero-dependency startup profiler for Node.js that instruments CommonJS and ESM startup loading, reconstructs the dependency tree, and points at the modules that actually slow boot time down.
11
+
12
+ Terminal tree, JSON, and self-contained flamegraph output are included.
13
+
14
+ It is designed for questions like:
15
+
16
+ - Why did startup go from `300ms` to `900ms`?
17
+ - Which dependency chain is dominating boot?
18
+ - Is the time going into my code or `node_modules`?
19
+ - Which modules are slow themselves vs slow only because of their children?
20
+ - Is startup blocking the event loop?
21
+
22
+ ## Highlights
23
+
24
+ - Times every CommonJS `require()` with `performance.now()`
25
+ - Traces ESM startup loads through Node's loader hooks
26
+ - Builds a parent → child module load tree
27
+ - Computes inclusive and exclusive time per module
28
+ - Tracks builtins like `fs`, `path`, and `http`
29
+ - Measures event loop delay during startup with `perf_hooks`
30
+ - Ships a terminal report, JSON report, and self-contained HTML flamegraph
31
+ - Has no runtime dependencies
32
+
33
+ ## Status
34
+
35
+ What works today:
36
+
37
+ - CommonJS startup profiling
38
+ - ESM startup profiling
39
+ - CLI profiling
40
+ - programmatic API
41
+ - text report
42
+ - JSON report
43
+ - single-file flamegraph HTML export
44
+
45
+ What does not work yet:
46
+
47
+ - dynamic `import()` tracing
48
+
49
+ ## Requirements
50
+
51
+ - Node.js `18+`
52
+ - Node.js `18.19+` for ESM tracing through the loader hook path
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ npm install coldstart
58
+ ```
59
+
60
+ If you are working in this repository directly:
61
+
62
+ ```bash
63
+ npm install
64
+ npm run build
65
+ ```
66
+
67
+ ## Quick Start
68
+
69
+ ### CLI
70
+
71
+ Profile a Node app:
72
+
73
+ ```bash
74
+ coldstart server.js
75
+ ```
76
+
77
+ Profile an ESM entry:
78
+
79
+ ```bash
80
+ coldstart server.mjs
81
+ ```
82
+
83
+ Pass Node flags through `-- node ...`:
84
+
85
+ ```bash
86
+ coldstart -- node --trace-warnings server.js
87
+ ```
88
+
89
+ Print JSON instead of the text tree:
90
+
91
+ ```bash
92
+ coldstart --json server.js
93
+ ```
94
+
95
+ If you are running from this repo before publishing:
96
+
97
+ ```bash
98
+ node dist/cli.js server.js
99
+ ```
100
+
101
+ ### Programmatic API
102
+
103
+ ```ts
104
+ import { monitor, renderTextReport } from 'coldstart'
105
+
106
+ const done = monitor()
107
+
108
+ require('./bootstrap')
109
+ require('./server')
110
+
111
+ const report = done()
112
+ console.log(renderTextReport(report))
113
+ ```
114
+
115
+ ### Preload Mode
116
+
117
+ If you want just the loader patch:
118
+
119
+ ```bash
120
+ node --require coldstart/register server.js
121
+ ```
122
+
123
+ For ESM or mixed apps, prefer:
124
+
125
+ ```bash
126
+ node --import coldstart/register server.mjs
127
+ ```
128
+
129
+ From this repo:
130
+
131
+ ```bash
132
+ node --require ./dist/register.js server.js
133
+ ```
134
+
135
+ ```bash
136
+ node --import ./dist/register.mjs server.mjs
137
+ ```
138
+
139
+ ## First Useful Test
140
+
141
+ Builtins are useful for verifying that the hook is active, but they are usually too fast to teach you much.
142
+
143
+ This works as a smoke test:
144
+
145
+ ```bash
146
+ node dist/cli.js -- node -e "require('module')"
147
+ ```
148
+
149
+ But this is a better test because it loads real file-backed code:
150
+
151
+ ```bash
152
+ node dist/cli.js -- node -e "require('typescript')"
153
+ ```
154
+
155
+ For ESM:
156
+
157
+ ```bash
158
+ node dist/cli.js -- node --input-type=module -e "await import('./dist/index.mjs')"
159
+ ```
160
+
161
+ Or:
162
+
163
+ ```bash
164
+ node dist/cli.js -- node -e "require('express'); require('sequelize')"
165
+ ```
166
+
167
+ If all you test is `fs`, `path`, or `http`, seeing `0ms` is expected.
168
+
169
+ ## Example Output
170
+
171
+ ```text
172
+ coldstart - 847ms total startup
173
+
174
+ ┌─ express 234ms ████████████░░░░░░
175
+ │ ├─ body-parser 89ms █████░░░░░░░░░░░░
176
+ │ └─ qs 12ms █░░░░░░░░░░░░░░░░
177
+ └─ sequelize 401ms █████████████████ ! slow
178
+
179
+ event loop max 42ms, p99 17ms, mean 4.3ms
180
+ modules 312 total, 59 cached
181
+ time split 286ms first-party, 503ms node_modules
182
+ ```
183
+
184
+ Color thresholds in the text reporter:
185
+
186
+ - green: under `20ms`
187
+ - yellow: `20ms` to `100ms`
188
+ - red: over `100ms`
189
+
190
+ ## CLI Usage
191
+
192
+ ```bash
193
+ coldstart [--json] [--color] [--no-color] app.js [args...]
194
+ coldstart [--json] [--color] [--no-color] -- node [node-flags...] app.js [args...]
195
+ ```
196
+
197
+ Examples:
198
+
199
+ ```bash
200
+ coldstart app.js
201
+ coldstart app.js --port 3000
202
+ coldstart --json app.js
203
+ coldstart -- node --inspect app.js
204
+ ```
205
+
206
+ What the CLI does:
207
+
208
+ 1. spawns your app in a child Node process
209
+ 2. preloads `coldstart/register`
210
+ 3. lets the app run normally
211
+ 4. collects the startup report on exit
212
+ 5. prints either text or JSON
213
+
214
+ ## Programmatic API
215
+
216
+ ### `monitor()`
217
+
218
+ Starts a fresh measurement and returns a function that finalizes the report.
219
+
220
+ ```ts
221
+ import { monitor } from 'coldstart'
222
+
223
+ const done = monitor()
224
+ const report = done()
225
+ ```
226
+
227
+ ### `report()`
228
+
229
+ Returns the current report immediately.
230
+
231
+ ```ts
232
+ import { report } from 'coldstart'
233
+
234
+ const startupReport = report()
235
+ ```
236
+
237
+ This is mainly useful if you already enabled the hook separately.
238
+
239
+ ### `renderTextReport(report, options?)`
240
+
241
+ Returns the terminal report as a string.
242
+
243
+ ```ts
244
+ import { renderTextReport } from 'coldstart'
245
+
246
+ console.log(renderTextReport(report))
247
+ ```
248
+
249
+ Options:
250
+
251
+ - `color?: boolean`
252
+ - `barWidth?: number`
253
+ - `showSummary?: boolean`
254
+
255
+ ### `renderJsonReport(report, options?)`
256
+
257
+ Returns the report as JSON.
258
+
259
+ ```ts
260
+ import { renderJsonReport } from 'coldstart'
261
+
262
+ process.stdout.write(renderJsonReport(report))
263
+ ```
264
+
265
+ Options:
266
+
267
+ - `pretty?: boolean`
268
+
269
+ ### `renderFlamegraphHtml(report)`
270
+
271
+ Returns a self-contained HTML flamegraph.
272
+
273
+ ```ts
274
+ import { renderFlamegraphHtml } from 'coldstart'
275
+ import { writeFileSync } from 'fs'
276
+
277
+ writeFileSync('coldstart.html', renderFlamegraphHtml(report))
278
+ ```
279
+
280
+ Open the generated file in a browser to inspect the startup timeline visually.
281
+
282
+ ## How It Works
283
+
284
+ `coldstart` records raw startup load events from both the CommonJS loader and the ESM loader hook path.
285
+
286
+ For CommonJS:
287
+
288
+ - the request is resolved
289
+ - the parent module is tracked
290
+ - wall-clock load time is measured with `performance.now()`
291
+ - cache hits are recorded
292
+ - builtins are recorded as dependency edges
293
+
294
+ For ESM:
295
+
296
+ - module resolution is captured through the loader `resolve` hook
297
+ - module execution completion is tracked through the loader `load` hook
298
+ - timing is bridged back to the main tracer through a message channel
299
+
300
+ After startup, the tracer converts those raw events into:
301
+
302
+ - a tree of module loads
303
+ - a flat list sorted by inclusive time
304
+ - a slowest list sorted by exclusive time
305
+ - summary stats for total startup, cached loads, event loop blocking, first-party time, and `node_modules` time
306
+
307
+ ## Understanding the Numbers
308
+
309
+ Each module node includes both `inclusiveMs` and `exclusiveMs`.
310
+
311
+ Use `inclusiveMs` to answer:
312
+
313
+ - Which subtree made startup slow?
314
+ - Which dependency chain dominates boot time?
315
+
316
+ Use `exclusiveMs` to answer:
317
+
318
+ - Which module itself is slow?
319
+ - Where is the actual initialization work happening?
320
+
321
+ Rule of thumb:
322
+
323
+ - high inclusive, low exclusive: the module mostly pulls in slow children
324
+ - high exclusive: the module itself is expensive
325
+
326
+ ## Report Shape
327
+
328
+ ```ts
329
+ interface StartupReport {
330
+ totalStartupMs: number
331
+ eventLoop: {
332
+ maxBlockMs: number
333
+ meanBlockMs: number
334
+ p99BlockMs: number
335
+ }
336
+ tree: ModuleNode[]
337
+ flat: ModuleNode[]
338
+ slowest: ModuleNode[]
339
+ nodeModuleTime: number
340
+ firstPartyTime: number
341
+ totalModulesLoaded: number
342
+ cachedModulesCount: number
343
+ }
344
+ ```
345
+
346
+ ```ts
347
+ interface ModuleNode {
348
+ id: string
349
+ request: string
350
+ resolvedPath: string
351
+ parentPath: string
352
+ inclusiveMs: number
353
+ exclusiveMs: number
354
+ cached: boolean
355
+ isNodeModule: boolean
356
+ isBuiltin: boolean
357
+ children: ModuleNode[]
358
+ depth: number
359
+ }
360
+ ```
361
+
362
+ ## Troubleshooting
363
+
364
+ ### Why do builtins like `fs`, `path`, or `http` show `0ms`?
365
+
366
+ Because builtins are not loaded from disk like normal package files. They are still useful to record as dependency edges, but their measured cost is usually effectively zero.
367
+
368
+ ### Why do I mostly see builtin modules?
369
+
370
+ Your test is probably too small. Try profiling real modules instead:
371
+
372
+ ```bash
373
+ coldstart -- node -e "require('typescript')"
374
+ ```
375
+
376
+ or:
377
+
378
+ ```bash
379
+ coldstart app.js
380
+ ```
381
+
382
+ where `app.js` loads real package or first-party code.
383
+
384
+ ### Why do I not see ESM imports?
385
+
386
+ Make sure you are on Node `18.19+`. The ESM path depends on `module.register()` and Node's loader hooks.
387
+
388
+ Also note that current support is for startup ESM loading. Dynamic `import()` tracing is still not implemented.
389
+
390
+ ### Why is the CLI output short when my app keeps running?
391
+
392
+ The CLI prints the report when the child process exits. It is intended for startup profiling runs, not long-lived always-on monitoring.
393
+
394
+ If you need more control, use the programmatic API and decide when to call `done()`.
395
+
396
+ ### Why do cached modules appear at all?
397
+
398
+ Because cached loads still describe the startup dependency graph. In text output, noisy zero-cost cached builtin rows are collapsed to keep the tree readable.
399
+
400
+ ## Development
401
+
402
+ Build:
403
+
404
+ ```bash
405
+ npm run build
406
+ ```
407
+
408
+ Type-check:
409
+
410
+ ```bash
411
+ npx tsc -p tsconfig.json
412
+ ```
413
+
414
+ Smoke test:
415
+
416
+ ```bash
417
+ node dist/cli.js -- node -e "require('typescript')"
418
+ ```
419
+
420
+ ESM smoke test:
421
+
422
+ ```bash
423
+ node dist/cli.js -- node --input-type=module -e "await import('./dist/index.mjs')"
424
+ ```
425
+
426
+ Programmatic smoke test:
427
+
428
+ ```bash
429
+ node -e "const { monitor, renderTextReport } = require('./dist/index.js'); const done = monitor(); require('typescript'); console.log(renderTextReport(done()));"
430
+ ```
431
+
432
+ ## Why This Exists
433
+
434
+ Startup regressions are easy to introduce and easy to miss.
435
+
436
+ A single heavy dependency, synchronous work in module scope, or a deep require chain can quietly add hundreds of milliseconds before your app is ready. `coldstart` makes that visible fast.
437
+
438
+ ## License
439
+
440
+ MIT