@progen-labs/spectra 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +99 -2
- package/bin/spectra.js +215 -0
- package/dist/assets/index-BvBsPok5.js +46 -0
- package/dist/assets/index-Da3Le5Px.css +1 -0
- package/dist/index.html +14 -0
- package/index.html +13 -0
- package/package.json +76 -4
- package/server/analysis/cpu.js +369 -0
- package/server/analysis/findings.js +551 -0
- package/server/analysis/locate.js +99 -0
- package/server/analysis/report.js +750 -0
- package/server/analysis/sources.js +249 -0
- package/server/analysis/trace.js +314 -0
- package/server/api.js +292 -0
- package/server/chrome.js +81 -0
- package/server/cli.js +94 -0
- package/server/config.js +59 -0
- package/server/dashboard.js +154 -0
- package/server/explore/boot.js +150 -0
- package/server/explore/classify.js +169 -0
- package/server/explore/discover.js +162 -0
- package/server/explore/evaluate-cli.js +101 -0
- package/server/explore/evaluate.js +165 -0
- package/server/explore/paths.js +105 -0
- package/server/explore/runner.js +94 -0
- package/server/explore/test-explore.js +139 -0
- package/server/explore/test-paths.js +75 -0
- package/server/framework.js +67 -0
- package/server/git.js +59 -0
- package/server/pdf/render.js +60 -0
- package/server/pdf/template.js +419 -0
- package/server/plugin.js +19 -0
- package/server/probe.browser.js +689 -0
- package/server/recorder.js +610 -0
- package/server/signin.js +27 -0
- package/server/test-analysis.js +424 -0
- package/server/test-compare.js +473 -0
- package/server/test-sources.js +136 -0
- package/shared/compare.js +977 -0
- package/shared/snapshot.js +77 -0
- package/src/App.jsx +361 -0
- package/src/api.js +51 -0
- package/src/components/AppearanceMenu.jsx +76 -0
- package/src/components/CodeRef.jsx +48 -0
- package/src/components/ComparePanel.jsx +143 -0
- package/src/components/CompareResults.jsx +473 -0
- package/src/components/ControlBar.jsx +336 -0
- package/src/components/CulpritSphere.jsx +218 -0
- package/src/components/Culprits.jsx +118 -0
- package/src/components/CulpritsHero.jsx +109 -0
- package/src/components/EvaluationPanel.jsx +160 -0
- package/src/components/FilePanel.jsx +225 -0
- package/src/components/FindingsList.jsx +381 -0
- package/src/components/HeatTrees.jsx +222 -0
- package/src/components/Icons.jsx +58 -0
- package/src/components/LongTasks.jsx +199 -0
- package/src/components/NetworkPanel.jsx +98 -0
- package/src/components/PageMap.jsx +329 -0
- package/src/components/Popover.jsx +51 -0
- package/src/components/ReactPanel.jsx +200 -0
- package/src/components/SidePicker.jsx +207 -0
- package/src/components/SnapshotButton.jsx +140 -0
- package/src/components/TimeBreakdown.jsx +45 -0
- package/src/components/TraceButton.jsx +76 -0
- package/src/components/TreeView.jsx +173 -0
- package/src/components/Vitals.jsx +163 -0
- package/src/culprits.js +127 -0
- package/src/download.js +18 -0
- package/src/format.js +26 -0
- package/src/heat.js +177 -0
- package/src/highlight.js +45 -0
- package/src/hooks/useCompare.js +187 -0
- package/src/hooks/useSession.js +73 -0
- package/src/layout.js +55 -0
- package/src/main.jsx +15 -0
- package/src/styles.css +2478 -0
- package/src/theme.js +75 -0
- package/src/themes.css +286 -0
- package/vite.config.js +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sourav Debnath
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction rights, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,100 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @progen-labs/spectra
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Find out which file, which function and which component makes your frontend slow — automatically.**
|
|
4
|
+
|
|
5
|
+
Spectra is a CLI and dashboard that profiles a frontend build through the Chrome DevTools Protocol
|
|
6
|
+
(tracing + CPU sampling) plus a small in-page probe for component re-renders and Web Vitals. Run one
|
|
7
|
+
command and it starts your dev server, auto-discovers the actionable elements on the page, drives a set
|
|
8
|
+
of **golden-path journeys**, records each one, and reports a ranked list of culprits — each with the
|
|
9
|
+
exact function, file and line, the evidence, and a suggested fix.
|
|
10
|
+
|
|
11
|
+
Works with **React, Vue, Svelte, Angular and Next.js** builds (component-level attribution is deepest on
|
|
12
|
+
React today; a source-mapped generic profile covers the rest). A [Progen Labs](https://github.com/progen-labs)
|
|
13
|
+
project — open source, non-profit, no telemetry, no account required.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install --save-dev @progen-labs/spectra
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Requirements: **Node 18+** and a local **Chrome / Chromium / Edge** (Spectra drives your installed
|
|
22
|
+
browser; it does not download one). If it can't find Chrome, set `CHROME_PATH` to the executable.
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
From the root of your project:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx spectra performance
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
With no `--url`, Spectra:
|
|
33
|
+
|
|
34
|
+
1. detects your framework and starts your dev server (`npm run dev`/`start`/`serve`),
|
|
35
|
+
2. records a baseline page load,
|
|
36
|
+
3. auto-discovers the actionable elements (links, buttons, inputs, forms),
|
|
37
|
+
4. synthesizes and runs **golden-path journeys** — safely (see below),
|
|
38
|
+
5. prints a performance matrix and saves a report per journey, then stops the server.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
# Golden path Result TBT LCP LongT Top finding
|
|
42
|
+
— Initial page load PASS 0ms 128ms 0 [warning] main chunk served uncompressed
|
|
43
|
+
1 Click "Pricing" PASS 0ms — 0 [warning] Large DOM: 2,139 nodes
|
|
44
|
+
2 Click "Folders" PASS 0ms — 0 [critical] TreeNode re-rendered 1100× (96% wasted)
|
|
45
|
+
...
|
|
46
|
+
Automation: 8/8 golden paths passed.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Then open the visual dashboard — ranked culprits, React re-renders, long tasks, the golden-path matrix,
|
|
50
|
+
**PDF export** and **commit-to-commit compare**:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx spectra dashboard
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Commands
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
spectra performance [options] Golden-path evaluation (default)
|
|
60
|
+
spectra dashboard [--port N] Open the dashboard UI
|
|
61
|
+
spectra --help Full option list
|
|
62
|
+
spectra --version
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**`performance` options**
|
|
66
|
+
|
|
67
|
+
| Option | Description |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `--url <page>` | Evaluate an already-running page; skips self-boot |
|
|
70
|
+
| `--paths <n>` | Max golden paths to run (default 12) |
|
|
71
|
+
| `--command "<cmd>"` | Override the dev-server command |
|
|
72
|
+
| `--boot-timeout <s>` | How long to wait for the dev server (default 60) |
|
|
73
|
+
| `--headed` | Show the browser window |
|
|
74
|
+
| `--sign-in <url>` | Sign-in URL template, e.g. `http://host/login/?redirect={url}` |
|
|
75
|
+
| `--mode live\|load` | Classic single-page profile instead of the golden-path suite |
|
|
76
|
+
|
|
77
|
+
## Safe by default
|
|
78
|
+
|
|
79
|
+
Auto-exploration never triggers destructive actions. Spectra detects controls that would mutate state —
|
|
80
|
+
anything labelled delete / pay / checkout / log out, logout links, and `POST`/`PUT`/`DELETE` form submits —
|
|
81
|
+
and **skips** them unless you explicitly allow them. Form fields are filled with synthetic values; GET
|
|
82
|
+
searches and navigations are fair game.
|
|
83
|
+
|
|
84
|
+
## Where results go
|
|
85
|
+
|
|
86
|
+
Reports are written to `.spectra/reports/` in your project (add it to `.gitignore`). Each golden path is a
|
|
87
|
+
full report you can open in `spectra dashboard`, export to PDF, or compare against another commit.
|
|
88
|
+
|
|
89
|
+
## How it works
|
|
90
|
+
|
|
91
|
+
Spectra launches Chrome via `puppeteer-core`, records a CDP trace + CPU profile, and injects a probe that
|
|
92
|
+
tracks component renders, slow interactions and Web Vitals. It maps sampled frames back to your source
|
|
93
|
+
through source maps (and bundler markers as a fallback), then ranks the files, functions and components
|
|
94
|
+
that cost the most main-thread time. See the
|
|
95
|
+
[deep-dive docs](https://github.com/progen-labs/spectra/blob/main/DOCS.md) for how capture, correlation
|
|
96
|
+
and report generation work.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT © Progen Labs
|
package/bin/spectra.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Spectra CLI — a Progen Labs project.
|
|
4
|
+
*
|
|
5
|
+
* spectra performance [--url <page>] [profiler options] profile a frontend
|
|
6
|
+
* spectra dashboard open the dashboard UI
|
|
7
|
+
* spectra --help show this help
|
|
8
|
+
* spectra --version print the version
|
|
9
|
+
*
|
|
10
|
+
* When Spectra is installed in a project, it profiles THAT project: reports are
|
|
11
|
+
* written under the current working directory (.spectra/reports) and the project
|
|
12
|
+
* root is used for source lookup, unless the matching PERF_* env var overrides it.
|
|
13
|
+
*/
|
|
14
|
+
import { spawn } from 'node:child_process';
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { bootDevServer } from '../server/explore/boot.js';
|
|
19
|
+
import { detectFramework } from '../server/framework.js';
|
|
20
|
+
|
|
21
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const PKG_ROOT = path.resolve(here, '..');
|
|
23
|
+
const pkg = JSON.parse(
|
|
24
|
+
readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const argv = process.argv.slice(2);
|
|
28
|
+
const command = argv[0];
|
|
29
|
+
const rest = argv.slice(1);
|
|
30
|
+
|
|
31
|
+
const HELP = `
|
|
32
|
+
Spectra ${pkg.version} — frontend performance profiler (Progen Labs)
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
spectra performance [options] Profile a frontend build and report the culprits
|
|
36
|
+
spectra dashboard Open the Spectra dashboard UI
|
|
37
|
+
spectra --help, -h Show this help
|
|
38
|
+
spectra --version, -v Print the version
|
|
39
|
+
|
|
40
|
+
Run it from inside your project. With no --url, Spectra detects the framework,
|
|
41
|
+
starts your dev server (npm run dev/start/serve), evaluates it, then shuts it
|
|
42
|
+
down. By default it records a baseline page load, auto-discovers the actionable
|
|
43
|
+
elements, synthesizes golden-path journeys, runs them (safe actions only), and
|
|
44
|
+
prints a performance matrix. Every path is saved as a report you can open in
|
|
45
|
+
the dashboard. Reports go to .spectra/reports in the current directory.
|
|
46
|
+
|
|
47
|
+
performance options:
|
|
48
|
+
--url <page> Evaluate an already-running page; skips self-boot
|
|
49
|
+
--paths <n> Max golden paths to run (default 12)
|
|
50
|
+
--command "<cmd>" Override the dev-server command (default: your npm dev script)
|
|
51
|
+
--boot-timeout <s> How long to wait for the dev server (default 60)
|
|
52
|
+
--headed Show the browser window
|
|
53
|
+
--sign-in <url> Sign-in URL template, e.g. "http://host/login/?redirect={url}"
|
|
54
|
+
|
|
55
|
+
classic single-page profile (opt in with --mode or --scenario):
|
|
56
|
+
--mode live|load Live session or page-load recording
|
|
57
|
+
--duration <seconds> Live recording length (default 8)
|
|
58
|
+
--throttle <n> CPU throttle multiplier (default 1)
|
|
59
|
+
--scenario <file.mjs> Script a live session: export default async page => {…}
|
|
60
|
+
--save-trace Keep the raw Chrome trace alongside the report
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
spectra performance # self-boot + golden-path suite
|
|
64
|
+
spectra performance --paths 8 # cap the suite at 8 journeys
|
|
65
|
+
spectra performance --url http://localhost:3000/ # evaluate a running server
|
|
66
|
+
spectra performance --mode load # classic single page-load profile
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
function runNodeAsync(scriptRelPath, args, extraEnv = {}) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const child = spawn(
|
|
72
|
+
process.execPath,
|
|
73
|
+
[path.join(PKG_ROOT, scriptRelPath), ...args],
|
|
74
|
+
{
|
|
75
|
+
cwd: PKG_ROOT,
|
|
76
|
+
stdio: 'inherit',
|
|
77
|
+
env: { ...process.env, ...extraEnv },
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
child.on('exit', code => resolve(code ?? 0));
|
|
81
|
+
child.on('error', reject);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Pull `--name value` out of an argv array, returning the value and the rest. */
|
|
86
|
+
function takeOption(args, name) {
|
|
87
|
+
const i = args.indexOf(`--${name}`);
|
|
88
|
+
if (i === -1) return { value: undefined, rest: args };
|
|
89
|
+
const copy = args.slice();
|
|
90
|
+
const [, value] = copy.splice(i, 2);
|
|
91
|
+
return { value, rest: copy };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Default the profiled project and the reports location to the user's current
|
|
96
|
+
* directory, so an installed Spectra profiles the host project and writes
|
|
97
|
+
* reports there rather than inside node_modules. Explicit PERF_* vars win.
|
|
98
|
+
*/
|
|
99
|
+
function projectEnv() {
|
|
100
|
+
const cwd = process.cwd();
|
|
101
|
+
const env = {};
|
|
102
|
+
if (!process.env.PERF_FRONTEND_DIR && !process.env.FRONTEND_DIR) {
|
|
103
|
+
env.PERF_FRONTEND_DIR = cwd;
|
|
104
|
+
}
|
|
105
|
+
if (!process.env.PERF_REPORTS_DIR) {
|
|
106
|
+
env.PERF_REPORTS_DIR = path.join(cwd, '.spectra', 'reports');
|
|
107
|
+
}
|
|
108
|
+
return env;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function runProfile() {
|
|
112
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
113
|
+
console.log(HELP);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
const env = projectEnv();
|
|
117
|
+
|
|
118
|
+
// Golden-path evaluation is the default. --mode/--scenario opt into the
|
|
119
|
+
// classic single-page profile instead.
|
|
120
|
+
const classic = rest.includes('--mode') || rest.includes('--scenario');
|
|
121
|
+
const script = classic ? 'server/cli.js' : 'server/explore/evaluate-cli.js';
|
|
122
|
+
|
|
123
|
+
// An explicit --url means the server is already running: run against it.
|
|
124
|
+
if (rest.includes('--url')) {
|
|
125
|
+
return runNodeAsync(script, rest, env);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// No --url: self-boot the target project's own dev server, run, stop.
|
|
129
|
+
const cwd = process.cwd();
|
|
130
|
+
const { value: command, rest: afterCmd } = takeOption(rest, 'command');
|
|
131
|
+
const { value: bootTimeout, rest: profileArgs } = takeOption(afterCmd, 'boot-timeout');
|
|
132
|
+
const fw = detectFramework(cwd);
|
|
133
|
+
const label =
|
|
134
|
+
fw.framework !== 'unknown' ? `${fw.framework}/${fw.bundler}` : fw.bundler;
|
|
135
|
+
// Status goes to stderr so it never mixes with the report on stdout.
|
|
136
|
+
console.error(
|
|
137
|
+
`spectra: no --url given — starting the ${label} dev server in ${cwd} …`,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
let server;
|
|
141
|
+
try {
|
|
142
|
+
server = await bootDevServer({
|
|
143
|
+
root: cwd,
|
|
144
|
+
command,
|
|
145
|
+
timeoutMs: bootTimeout ? Number(bootTimeout) * 1000 : 60_000,
|
|
146
|
+
});
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.error(`spectra: ${err.message}`);
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
console.error(`spectra: dev server ready at ${server.url} — profiling …\n`);
|
|
152
|
+
|
|
153
|
+
let code = 1;
|
|
154
|
+
try {
|
|
155
|
+
code = await runNodeAsync(
|
|
156
|
+
script,
|
|
157
|
+
[...profileArgs, '--url', server.url],
|
|
158
|
+
env,
|
|
159
|
+
);
|
|
160
|
+
} finally {
|
|
161
|
+
await server.stop();
|
|
162
|
+
console.error('\nspectra: stopped the dev server.');
|
|
163
|
+
}
|
|
164
|
+
return code;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function runDashboard() {
|
|
168
|
+
// Standalone server: serves the prebuilt dashboard and the backend API, so an
|
|
169
|
+
// installed Spectra needs no dev toolchain. projectEnv() points it at this
|
|
170
|
+
// project's reports and source roots.
|
|
171
|
+
const child = spawn(
|
|
172
|
+
process.execPath,
|
|
173
|
+
[path.join(PKG_ROOT, 'server/dashboard.js'), ...rest],
|
|
174
|
+
{
|
|
175
|
+
cwd: PKG_ROOT,
|
|
176
|
+
stdio: 'inherit',
|
|
177
|
+
env: { ...process.env, ...projectEnv() },
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
child.on('exit', code => process.exit(code ?? 0));
|
|
181
|
+
child.on('error', err => {
|
|
182
|
+
console.error(`spectra: failed to start the dashboard — ${err.message}`);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
switch (command) {
|
|
188
|
+
case 'performance':
|
|
189
|
+
case 'perf':
|
|
190
|
+
runProfile()
|
|
191
|
+
.then(code => process.exit(code ?? 0))
|
|
192
|
+
.catch(err => {
|
|
193
|
+
console.error(`spectra: ${err.message}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
});
|
|
196
|
+
break;
|
|
197
|
+
case 'dashboard':
|
|
198
|
+
case 'ui':
|
|
199
|
+
runDashboard();
|
|
200
|
+
break;
|
|
201
|
+
case '--version':
|
|
202
|
+
case '-v':
|
|
203
|
+
console.log(pkg.version);
|
|
204
|
+
break;
|
|
205
|
+
case undefined:
|
|
206
|
+
case '--help':
|
|
207
|
+
case '-h':
|
|
208
|
+
case 'help':
|
|
209
|
+
console.log(HELP);
|
|
210
|
+
break;
|
|
211
|
+
default:
|
|
212
|
+
console.error(`spectra: unknown command "${command}"`);
|
|
213
|
+
console.log(HELP);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|