ref(browser): Replace vendored web-vitals with web-vitals@6 dependency - #23070
Conversation
d23f63e to
64997f2
Compare
size-limit report 📦
|
b2a21b4 to
a786f2c
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a786f2c. Configure here.
Upstream web-vitals v6 covers the metrics we vendored, so consume it as a dependency instead of maintaining a ~1.9k-line in-tree copy. instrument.ts imports the metric functions from the package and observes performance entries with a local PerformanceObserver; the handful of generic browser helpers still needed (getNavigationEntry, getVisibilityWatcher, whenIdleOrHidden, etc.) move to metrics/web-vitals-helpers, and the rest of the vendored code is deleted. No behavior change: browser-utils unit tests and the browser-integration web-vitals suites (LCP/CLS/INP/FCP/TTFB + streamed spans) all pass.
The vendored `observe` helper wrapped PerformanceObserver callbacks in a microtask to work around a Safari bug where the callback fires synchronously during `observe()` instead of in a separate task (GoogleChrome/web-vitals#277). The local `instrumentPerformanceObserver` dropped that defer, so paint/longtask/event/ element handlers could run synchronously at init on affected Safari versions. Upstream web-vitals@6 still ships this defer, so restore it here to match.
`instrumentInp` returns the `StopListening` cleanup from `onINP`, matching its `instrumentCls`/`instrumentLcp`/`instrumentTtfb` siblings and the `addMetricObserver` `instrumentFn` parameter, but was typed `void`. That silently discarded the cleanup so a future `stopOnCallback` on INP would no-op.
a7f3f5f to
732fddb
Compare
| // callback is invoked synchronously rather than in a separate task. | ||
| // See: https://github.com/GoogleChrome/web-vitals/issues/277 | ||
| void Promise.resolve().then(() => { | ||
| triggerHandlers(type, { entries: list.getEntries() }); | ||
| }); | ||
| }); | ||
| po.observe(options); | ||
| } | ||
| } catch { | ||
| // Unsupported entry type; nothing to observe. | ||
| } |
There was a problem hiding this comment.
Bug: A TypeError will be thrown in browsers where PerformanceObserver.supportedEntryTypes is undefined, causing performance observer initialization to fail silently.
Severity: MEDIUM
Suggested Fix
Use optional chaining to safely access the includes method: PerformanceObserver.supportedEntryTypes?.includes(type). This prevents the TypeError in environments where supportedEntryTypes is not defined, allowing the code to fail gracefully.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/browser-utils/src/metrics/instrument.ts#L311-L335
Potential issue: The `instrumentPerformanceObserver` function directly accesses
`PerformanceObserver.supportedEntryTypes.includes(type)`. In environments where
`PerformanceObserver` exists but the `supportedEntryTypes` property is `undefined`
(e.g., older browsers), this will throw a `TypeError`. The surrounding `try/catch` block
will swallow the error, but it will silently prevent the performance observer from being
initialized, leading to a loss of performance metrics from affected user agents.
Reorganizes `browser-utils/src` away from the catch-all `metrics/` folder into clear domains: `instrumentation/` (dom/history/location/xhr + the PerformanceObserver layer), `web-vitals/` (tracking, spans, inp, lcp, reportEvents, and the helpers merged into one utils), and `performance/` (entries, element/user/resource timing, shared utils). The reasoning is we will actually have some metric emitting logic in there and we don't want it to be confused with pre-existing logic like in #22397 Following up on pulling in `webvitals` as a dependency, I took this chance to clean up our structure and it does have some minor bundle-size improvements. The bundle increases are due to #23070 which explains where the increase is coming from and why we are willing to absorb it.

Replaces the vendored web-vitals fork with
web-vitals@6as a dependency.No behavior change. browser-utils unit tests and the browser-integration web-vitals suites (LCP/CLS/INP/FCP/TTFB + streamed spans) pass, plus we still gate the bfcache web vitals from being sent, so identical behavior as of today.
A couple of concerns we discussed: