Extend devfeedback to the whole local dev cycle (Vite + Rspack) - #48
Merged
Conversation
Every event this package produced described one compilation, so the number a developer actually feels -- git pull to a working app -- was unanswerable, and in dev mode time to a usable dev server was not measured at all because closeBundle never fires. Adds a `type: "command"` event with an install, devserver or clientready phase, a sessionId on every event type so those phases stitch into one timeline, and install capture for npm, yarn and pnpm. Aborted runs are recorded too: Ctrl-C produces a devserver event with `signal` set, without changing what Ctrl-C does. Nothing here may sit on the critical path, which is most of the design: - install and signal-handler events are spooled to a local NDJSON file and delivered by the next dev server or build, so an install never waits on the network and a dying process never loses the race - 1500 ms timeout on every POST, so an off-VPN developer never hangs - git metadata is cached until the repository actually changes, replacing three synchronous git spawns per HMR event - the session id is resolved once per process; its keepalive touch is deferred, leaving the HMR path with no synchronous filesystem writes - stats.toJson() no longer serializes the whole compilation on every rebuild - startup chatter moved behind DEVFEEDBACK_DEBUG Install capture has two tiers. The default needs no repo change and infers the duration from the package manager's process start time. A preinstall/postinstall pair in the consuming repo upgrades it to an exact span with a trustworthy coldInstall flag; when those are present the bundled hook stands down, so one event is produced rather than two. measurementSource records which route was used, because the inferred one systematically undercounts and the two must not be aggregated together. Also fixes rsbuild and rspack posting to the webpack endpoint, contradicting the README, and widens the Vite peer range to >=4.0.0. packages/webpack-plugin is deliberately untouched. Closes #46. Ingestion-side schema changes are tracked in #47. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ratchapol-an
approved these changes
Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #46. Ingestion-side schema changes are tracked in #47.
getCommonMetadata()already captured timing, git, host and CPU beautifully, but every event described one compilation. So "how long fromgit pullto a working app?" was unanswerable, install time was invisible, and in dev modecloseBundlenever fires — meaning time to a usable dev server was not measured at all.packages/webpack-pluginis deliberately untouched, per the issue's non-goal.What lands
A new
type: "command"event onCOMMAND_ENDPOINT(defaulthttp://compilation-metrics/command):phaseinstalldevserverclientreadyPlus a
sessionIdon every event type — including the existingwebpack,vite,vitehmr,rspackandrsbuildpayloads — so those phases stitch into one timeline. Purely additive; no existing payload changed shape.Aborted runs are recorded too: Ctrl-C produces a
devserverevent withsignal: "SIGINT". A developer who gave up waiting is the most interesting point on the chart.Answers to the issue's open questions
1. Which package manager? All three. Every lockfile shape is hashed, all three are detected from
npm_config_user_agent, and §4b (scraping npm's own--timinglogs) is built since npm is in the set — that is where you find out a Playwright browser download or anode-gyprebuild is the actual cost.2. Option A vs B — cold coverage wanted, so A. A root
preinstall/postinstallpair gives the exact span and a trustworthycoldInstallflag. Option B still ships and needs no repo change; when the root hooks are present the bundled one stands down, so one event is produced rather than two. On a genuinely cold clone the preinstall file does not exist yet, thetry/catchswallows it, and the bundled hook falls back to process start time — cold clones are still measured, just less precisely. Every install event carriesmeasurementSource(preinstall/postinstall/npm-timing), because the inferred route systematically undercounts and the two must not be aggregated together.3. Vite range — widen. Peer is now
vite >=4.0.0,rollup >=3.0.0. Prebundling detection is tri-state and simply absent under Rolldown rather than guessing, and never reaches intoserver._optimizedDeps.4. Rspack endpoint — fixed.⚠️ If your dashboards read Rspack data off the webpack endpoint, they need repointing — #47 covers keeping both paths working during the transition, and it is worth checking how much existing "webpack" volume is actually rsbuild, since that skews historical webpack numbers.
rsbuild/rspacknow post to/rspackas the README always documented, instead of the webpack endpoint.5.
spooledAt— kept as a first-class field, and raw data preferred over precomputation.npmTimersships the raw map rather than a rollup, and #47 argues for a child table over storing a fixed subset.Never on the critical path
This is most of the design, because telemetry that slows people down gets deleted from configs:
HEAD/ref/packed-refs/config), replacing three synchronousgitspawns per HMR event.setImmediate— so the HMR path performs zero synchronous filesystem writes and zero process spawns.SIGINTlistener would otherwise suppress Node's default exit and leave the dev server hanging.stats.toJson()in the Rsbuild plugin no longer serializes the entire compilation on every rebuild.DEVFEEDBACK_DEBUG. No new stdout/stderr at the default log level.Testing
64 tests, 12/12 turbo tasks green (
check-types+test). New coverage: session lifecycle and TTL rollover, spool round-trip including size cap, age cutoff and malformed lines, lockfile hashing and package-manager parsing for all three managers, git cache invalidation on commit and branch switch, and the new dev-server/client-ready/abort paths in both plugins.Beyond unit tests, the install hooks were verified end-to-end against real temp repos: the cold path, the
--selfstand-down, and this repo's ownpnpm installrunning the bundled hook without noise or failure.One bug found and fixed during that smoke test: priming the session before reading state made
coldInstallalways reportfalseon the Option B path, becausegetSessionIdwrites the very state file that was about to be checked.Rollout notes
~/.config/pnpm/config.yaml(allowBuilds, oronlyBuiltDependencieson pnpm 10 and earlier) so repos stay untouched.agoda-devfeedback-commonas a direct devDependency — under pnpm a transitive dependency is not resolvable from the repo root.--ignore-scriptsskips install capture entirely.🤖 Generated with Claude Code