@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/LICENSE +21 -0
- package/dist/chunk-7HD2T2AD.mjs +261 -0
- package/dist/chunk-B5TADGOF.mjs +12 -0
- package/dist/chunk-IGYROJNQ.mjs +170 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +285 -0
- package/dist/cli.mjs +123 -0
- package/dist/esm-loader.d.mts +30 -0
- package/dist/esm-loader.d.ts +30 -0
- package/dist/esm-loader.js +125 -0
- package/dist/esm-loader.mjs +100 -0
- package/dist/index.d.mts +67 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +816 -0
- package/dist/index.mjs +368 -0
- package/dist/register.d.mts +2 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.js +438 -0
- package/dist/register.mjs +181 -0
- package/package.json +63 -0
- package/readme.md +440 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import {
|
|
2
|
+
tracer
|
|
3
|
+
} from "./chunk-7HD2T2AD.mjs";
|
|
4
|
+
import {
|
|
5
|
+
renderJsonReport,
|
|
6
|
+
renderTextReport
|
|
7
|
+
} from "./chunk-IGYROJNQ.mjs";
|
|
8
|
+
import "./chunk-B5TADGOF.mjs";
|
|
9
|
+
|
|
10
|
+
// src/reporter/flamegraph.ts
|
|
11
|
+
function renderFlamegraphHtml(report2) {
|
|
12
|
+
const total = Math.max(report2.totalStartupMs, 1);
|
|
13
|
+
const frames = buildFrames(report2.tree);
|
|
14
|
+
const maxDepth = frames.reduce((depth, frame) => Math.max(depth, frame.depth), 0);
|
|
15
|
+
const payload = JSON.stringify({
|
|
16
|
+
totalStartupMs: report2.totalStartupMs,
|
|
17
|
+
eventLoop: report2.eventLoop,
|
|
18
|
+
totalModulesLoaded: report2.totalModulesLoaded,
|
|
19
|
+
cachedModulesCount: report2.cachedModulesCount,
|
|
20
|
+
frames,
|
|
21
|
+
maxDepth
|
|
22
|
+
});
|
|
23
|
+
return `<!doctype html>
|
|
24
|
+
<html lang="en">
|
|
25
|
+
<head>
|
|
26
|
+
<meta charset="utf-8">
|
|
27
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
28
|
+
<title>coldstart flamegraph</title>
|
|
29
|
+
<style>
|
|
30
|
+
:root {
|
|
31
|
+
--bg: #f7f2e8;
|
|
32
|
+
--panel: rgba(255, 252, 246, 0.92);
|
|
33
|
+
--border: #d8c9ae;
|
|
34
|
+
--text: #2c2418;
|
|
35
|
+
--muted: #6b5c48;
|
|
36
|
+
--grid: rgba(117, 95, 63, 0.15);
|
|
37
|
+
--builtin: #8fb8a8;
|
|
38
|
+
--node-module: #e09f5a;
|
|
39
|
+
--first-party: #c8553d;
|
|
40
|
+
--cached: #b9b0a3;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
* { box-sizing: border-box; }
|
|
44
|
+
body {
|
|
45
|
+
margin: 0;
|
|
46
|
+
font-family: "IBM Plex Sans", "Avenir Next", sans-serif;
|
|
47
|
+
color: var(--text);
|
|
48
|
+
background:
|
|
49
|
+
radial-gradient(circle at top left, rgba(232, 171, 96, 0.28), transparent 28%),
|
|
50
|
+
radial-gradient(circle at top right, rgba(114, 155, 121, 0.18), transparent 25%),
|
|
51
|
+
linear-gradient(180deg, #fbf6ee 0%, var(--bg) 100%);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.page {
|
|
55
|
+
max-width: 1280px;
|
|
56
|
+
margin: 0 auto;
|
|
57
|
+
padding: 32px 20px 48px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.hero {
|
|
61
|
+
display: grid;
|
|
62
|
+
gap: 10px;
|
|
63
|
+
margin-bottom: 20px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
h1 {
|
|
67
|
+
margin: 0;
|
|
68
|
+
font-size: clamp(28px, 4vw, 48px);
|
|
69
|
+
line-height: 0.95;
|
|
70
|
+
letter-spacing: -0.04em;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.subtle {
|
|
74
|
+
color: var(--muted);
|
|
75
|
+
font-size: 14px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.stats {
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-wrap: wrap;
|
|
81
|
+
gap: 12px;
|
|
82
|
+
margin: 18px 0 24px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.stat {
|
|
86
|
+
background: var(--panel);
|
|
87
|
+
border: 1px solid var(--border);
|
|
88
|
+
border-radius: 14px;
|
|
89
|
+
padding: 12px 14px;
|
|
90
|
+
min-width: 150px;
|
|
91
|
+
backdrop-filter: blur(6px);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.stat strong {
|
|
95
|
+
display: block;
|
|
96
|
+
font-size: 20px;
|
|
97
|
+
margin-bottom: 4px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.legend {
|
|
101
|
+
display: flex;
|
|
102
|
+
flex-wrap: wrap;
|
|
103
|
+
gap: 12px;
|
|
104
|
+
margin-bottom: 14px;
|
|
105
|
+
font-size: 13px;
|
|
106
|
+
color: var(--muted);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.legend span {
|
|
110
|
+
display: inline-flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 8px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.swatch {
|
|
116
|
+
width: 12px;
|
|
117
|
+
height: 12px;
|
|
118
|
+
border-radius: 3px;
|
|
119
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.chart {
|
|
123
|
+
position: relative;
|
|
124
|
+
overflow: auto;
|
|
125
|
+
background: var(--panel);
|
|
126
|
+
border: 1px solid var(--border);
|
|
127
|
+
border-radius: 18px;
|
|
128
|
+
padding: 14px;
|
|
129
|
+
box-shadow: 0 20px 50px rgba(102, 75, 44, 0.08);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.chart-grid {
|
|
133
|
+
position: absolute;
|
|
134
|
+
inset: 14px;
|
|
135
|
+
pointer-events: none;
|
|
136
|
+
background-image:
|
|
137
|
+
linear-gradient(to right, var(--grid) 1px, transparent 1px),
|
|
138
|
+
linear-gradient(to bottom, rgba(117, 95, 63, 0.08) 1px, transparent 1px);
|
|
139
|
+
background-size: 10% 100%, 100% 32px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#flamegraph {
|
|
143
|
+
position: relative;
|
|
144
|
+
min-width: 960px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.frame {
|
|
148
|
+
position: absolute;
|
|
149
|
+
height: 26px;
|
|
150
|
+
border-radius: 6px;
|
|
151
|
+
overflow: hidden;
|
|
152
|
+
border: 1px solid rgba(44, 36, 24, 0.08);
|
|
153
|
+
cursor: default;
|
|
154
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.frame span {
|
|
158
|
+
display: block;
|
|
159
|
+
padding: 5px 8px;
|
|
160
|
+
white-space: nowrap;
|
|
161
|
+
overflow: hidden;
|
|
162
|
+
text-overflow: ellipsis;
|
|
163
|
+
font-size: 12px;
|
|
164
|
+
font-weight: 600;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.tooltip {
|
|
168
|
+
position: fixed;
|
|
169
|
+
z-index: 10;
|
|
170
|
+
max-width: 320px;
|
|
171
|
+
padding: 10px 12px;
|
|
172
|
+
border-radius: 12px;
|
|
173
|
+
background: rgba(44, 36, 24, 0.96);
|
|
174
|
+
color: #fff9ef;
|
|
175
|
+
font-size: 12px;
|
|
176
|
+
line-height: 1.4;
|
|
177
|
+
pointer-events: none;
|
|
178
|
+
opacity: 0;
|
|
179
|
+
transform: translateY(6px);
|
|
180
|
+
transition: opacity 120ms ease, transform 120ms ease;
|
|
181
|
+
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.25);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.tooltip.visible {
|
|
185
|
+
opacity: 1;
|
|
186
|
+
transform: translateY(0);
|
|
187
|
+
}
|
|
188
|
+
</style>
|
|
189
|
+
</head>
|
|
190
|
+
<body>
|
|
191
|
+
<div class="page">
|
|
192
|
+
<div class="hero">
|
|
193
|
+
<div class="subtle">Node.js startup profile</div>
|
|
194
|
+
<h1>coldstart flamegraph</h1>
|
|
195
|
+
<div class="subtle">Inclusive module load time laid out across startup from left to right.</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<div class="stats">
|
|
199
|
+
<div class="stat"><strong>${formatMs(report2.totalStartupMs)}</strong><span>Total startup</span></div>
|
|
200
|
+
<div class="stat"><strong>${report2.totalModulesLoaded}</strong><span>Modules loaded</span></div>
|
|
201
|
+
<div class="stat"><strong>${report2.cachedModulesCount}</strong><span>Cached requires</span></div>
|
|
202
|
+
<div class="stat"><strong>${formatMs(report2.eventLoop.maxBlockMs)}</strong><span>Max event loop block</span></div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div class="legend">
|
|
206
|
+
<span><i class="swatch" style="background: var(--first-party)"></i>First-party</span>
|
|
207
|
+
<span><i class="swatch" style="background: var(--node-module)"></i>node_modules</span>
|
|
208
|
+
<span><i class="swatch" style="background: var(--builtin)"></i>Builtins</span>
|
|
209
|
+
<span><i class="swatch" style="background: var(--cached)"></i>Cached</span>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<div class="chart">
|
|
213
|
+
<div class="chart-grid"></div>
|
|
214
|
+
<div id="flamegraph"></div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<div id="tooltip" class="tooltip"></div>
|
|
219
|
+
|
|
220
|
+
<script>
|
|
221
|
+
const data = ${payload};
|
|
222
|
+
const total = Math.max(data.totalStartupMs || ${total}, 1);
|
|
223
|
+
const rowHeight = 32;
|
|
224
|
+
const flamegraph = document.getElementById('flamegraph');
|
|
225
|
+
const tooltip = document.getElementById('tooltip');
|
|
226
|
+
flamegraph.style.height = ((data.maxDepth + 1) * rowHeight) + 'px';
|
|
227
|
+
|
|
228
|
+
const colorFor = (frame) => {
|
|
229
|
+
if (frame.cached) return 'var(--cached)';
|
|
230
|
+
if (frame.isBuiltin) return 'var(--builtin)';
|
|
231
|
+
if (frame.isNodeModule) return 'var(--node-module)';
|
|
232
|
+
return 'var(--first-party)';
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
for (const frame of data.frames) {
|
|
236
|
+
const element = document.createElement('div');
|
|
237
|
+
const width = Math.max((frame.value / total) * 100, 0.25);
|
|
238
|
+
element.className = 'frame';
|
|
239
|
+
element.style.left = (frame.start / total) * 100 + '%';
|
|
240
|
+
element.style.top = (frame.depth * rowHeight) + 'px';
|
|
241
|
+
element.style.width = width + '%';
|
|
242
|
+
element.style.background = colorFor(frame);
|
|
243
|
+
element.innerHTML = '<span>' + escapeHtml(frame.name) + '</span>';
|
|
244
|
+
element.addEventListener('mousemove', (event) => {
|
|
245
|
+
tooltip.innerHTML =
|
|
246
|
+
'<strong>' + escapeHtml(frame.name) + '</strong><br>' +
|
|
247
|
+
'Inclusive: ' + formatMs(frame.value) + '<br>' +
|
|
248
|
+
'Range: ' + formatMs(frame.start) + ' - ' + formatMs(frame.end) + '<br>' +
|
|
249
|
+
'Path: ' + escapeHtml(frame.path);
|
|
250
|
+
tooltip.style.left = (event.clientX + 14) + 'px';
|
|
251
|
+
tooltip.style.top = (event.clientY + 14) + 'px';
|
|
252
|
+
tooltip.classList.add('visible');
|
|
253
|
+
});
|
|
254
|
+
element.addEventListener('mouseleave', () => {
|
|
255
|
+
tooltip.classList.remove('visible');
|
|
256
|
+
});
|
|
257
|
+
flamegraph.appendChild(element);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function formatMs(value) {
|
|
261
|
+
if (!Number.isFinite(value)) return '0ms';
|
|
262
|
+
if (value >= 100) return Math.round(value) + 'ms';
|
|
263
|
+
if (value >= 10) return value.toFixed(1).replace(/\\.0$/, '') + 'ms';
|
|
264
|
+
return value.toFixed(2).replace(/0+$/, '').replace(/\\.$/, '') + 'ms';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function escapeHtml(value) {
|
|
268
|
+
return value
|
|
269
|
+
.replaceAll('&', '&')
|
|
270
|
+
.replaceAll('<', '<')
|
|
271
|
+
.replaceAll('>', '>')
|
|
272
|
+
.replaceAll('"', '"')
|
|
273
|
+
.replaceAll("'", ''');
|
|
274
|
+
}
|
|
275
|
+
</script>
|
|
276
|
+
</body>
|
|
277
|
+
</html>`;
|
|
278
|
+
}
|
|
279
|
+
function buildFrames(nodes) {
|
|
280
|
+
const frames = [];
|
|
281
|
+
for (const node of nodes) {
|
|
282
|
+
walk(node, 0, formatLabel(node));
|
|
283
|
+
}
|
|
284
|
+
return frames;
|
|
285
|
+
function walk(node, start, trail) {
|
|
286
|
+
const end = start + node.inclusiveMs;
|
|
287
|
+
frames.push({
|
|
288
|
+
name: formatLabel(node),
|
|
289
|
+
value: node.inclusiveMs,
|
|
290
|
+
depth: node.depth,
|
|
291
|
+
start,
|
|
292
|
+
end,
|
|
293
|
+
path: trail,
|
|
294
|
+
isBuiltin: node.isBuiltin,
|
|
295
|
+
isNodeModule: node.isNodeModule,
|
|
296
|
+
cached: node.cached
|
|
297
|
+
});
|
|
298
|
+
let childOffset = start;
|
|
299
|
+
for (const child of node.children) {
|
|
300
|
+
childOffset = walk(child, childOffset, `${trail} -> ${formatLabel(child)}`);
|
|
301
|
+
}
|
|
302
|
+
return end;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function formatLabel(node) {
|
|
306
|
+
if (node.isBuiltin) {
|
|
307
|
+
return node.request.replace(/^node:/, "");
|
|
308
|
+
}
|
|
309
|
+
if (node.isNodeModule) {
|
|
310
|
+
return packageNameFromRequest(node.request);
|
|
311
|
+
}
|
|
312
|
+
const normalizedPath = normalizeModulePath(node);
|
|
313
|
+
const segments = normalizedPath.split("/");
|
|
314
|
+
const base = segments[segments.length - 1] ?? "";
|
|
315
|
+
return base.length > 0 ? base : node.request || node.resolvedPath;
|
|
316
|
+
}
|
|
317
|
+
function normalizeModulePath(node) {
|
|
318
|
+
if (looksLikeRelativeRequest(node.request)) {
|
|
319
|
+
return node.request.replace(/\\/g, "/");
|
|
320
|
+
}
|
|
321
|
+
if (node.resolvedPath.startsWith("file://")) {
|
|
322
|
+
try {
|
|
323
|
+
return decodeURIComponent(new URL(node.resolvedPath).pathname).replace(/\\/g, "/");
|
|
324
|
+
} catch {
|
|
325
|
+
return node.resolvedPath.replace(/\\/g, "/");
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return node.resolvedPath.replace(/\\/g, "/");
|
|
329
|
+
}
|
|
330
|
+
function looksLikeRelativeRequest(request) {
|
|
331
|
+
return request.startsWith("./") || request.startsWith("../") || request.startsWith("/");
|
|
332
|
+
}
|
|
333
|
+
function packageNameFromRequest(request) {
|
|
334
|
+
if (request.startsWith("@")) {
|
|
335
|
+
const [scope, name2] = request.split("/");
|
|
336
|
+
return name2 ? `${scope}/${name2}` : request;
|
|
337
|
+
}
|
|
338
|
+
const [name] = request.split("/");
|
|
339
|
+
return name || request;
|
|
340
|
+
}
|
|
341
|
+
function formatMs(value) {
|
|
342
|
+
if (!Number.isFinite(value)) {
|
|
343
|
+
return "0ms";
|
|
344
|
+
}
|
|
345
|
+
if (value >= 100) {
|
|
346
|
+
return `${Math.round(value)}ms`;
|
|
347
|
+
}
|
|
348
|
+
if (value >= 10) {
|
|
349
|
+
return `${value.toFixed(1).replace(/\.0$/, "")}ms`;
|
|
350
|
+
}
|
|
351
|
+
return `${value.toFixed(2).replace(/0+$/, "").replace(/\.$/, "")}ms`;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/index.ts
|
|
355
|
+
function monitor() {
|
|
356
|
+
tracer.reset();
|
|
357
|
+
return () => tracer.report();
|
|
358
|
+
}
|
|
359
|
+
function report() {
|
|
360
|
+
return tracer.report();
|
|
361
|
+
}
|
|
362
|
+
export {
|
|
363
|
+
monitor,
|
|
364
|
+
renderFlamegraphHtml,
|
|
365
|
+
renderJsonReport,
|
|
366
|
+
renderTextReport,
|
|
367
|
+
report
|
|
368
|
+
};
|