feat: MFE core web-vitals detection#1768
Conversation
Supportability Metric Usage Changes:No matching changes found Supportability Metrics .md File Changes:supportability_metrics.md was changed? New supportability metrics require changes to supportability_metrics.md, as well as a new PR to Angler. Please ensure an Angler PR is created and linked to this PR. |
Asset Size Report
Merging this pull request will result in the following asset size changes:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1768 +/- ##
==========================================
+ Coverage 90.09% 90.12% +0.03%
==========================================
Files 214 215 +1
Lines 7852 7972 +120
Branches 1669 1736 +67
==========================================
+ Hits 7074 7185 +111
- Misses 708 716 +8
- Partials 70 71 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| let clsValue = 0 | ||
| const clsObs = observePerformance(observers, { type: 'layout-shift', buffered: true }, (entry) => { | ||
| if (entry.hadRecentInput || !vitals.cls) return | ||
| (entry.sources || []).some(source => { |
There was a problem hiding this comment.
Minor suggestion: Maybe consider using find() to achieve similar result/return only the first matching result in a given MFE. (This started as a "style" suggestion, but thought may be worth mentioning, as the code may be slightly shorter.)
|
I also saw LCP and INP are newly supported in Firefox and Safari. Might be good to mention in the PR notes. |
|
will have another followup PR to refine the output after evaluation and API review, to expose valuable attributes and remove unneeded ones |
Add internal detection methods for FCP, INP, CLS, and LCP when spawned by a MFE
Overview
This PR implements comprehensive Web Vitals tracking for Micro Frontend (MFE) components, adding detection and metadata collection for four Core Web Vitals metrics: First Contentful Paint (FCP), Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS), and Interaction to Next Paint (INP).
Key Changes
Unified Vitals Object Structure
Each vital metric is tracked as a unified object containing both the measurement value and rich contextual metadata. These objects are reported directly as JSON in the
MicroFrontEndTimingevent:MicroFrontEndTiming Event Attributes
When an MFE deregisters (via
deregister()API), aMicroFrontEndTimingevent is generated with the following JSON object attributes:Important: these attributes are prefixed with nr. to ensure they are hidden while being evaluated
nr.vitals.fcp(object or null):value- Time from script start to FCP (milliseconds)loadState- Document state when FCP occurred ('loading', 'interactive', or 'complete')nr.vitals.lcp(object or null):value- Time from script start to LCP (milliseconds)elTag- HTML tag of the LCP elementeid- Element ID attribute (if present)elUrl- URL for image/video elements or background imagessize- Element dimensions in pixels (width × height)nr.vitals.cls(object or null):value- Cumulative layout shift score (0-1+)largestShiftTarget- CSS selector of element causing largest shiftlargestShiftTime- Timestamp of largest individual shiftlargestShiftValue- Value of largest individual shiftloadState- Document state when CLS was measurednr.vitals.inp(object or null):value- Worst interaction latency in millisecondsinteractionTarget- CSS selector of interacted elementinteractionTime- Timestamp when interaction occurredinteractionType- Type of interaction (click, keydown, keypress, etc.)inputDelay- Delay before event processing began (ms)processingDuration- Duration of event handler execution (ms)presentationDelay- Delay from processing to paint (ms)nextPaintTime- Timestamp when paint occurredloadState- Document state during interactionsequenceDiagram participant App as MFE Application participant API as Register API participant Tracker as trackMFEVitals participant MO as MutationObserver participant PO as PerformanceObserver participant Events as Event Harvester App->>API: newrelic.register({ id, name }) API->>Tracker: trackMFEVitals(mfeId) Note over Tracker: Initialize vitals object<br/>{ fcp, lcp, cls, inp } = null Tracker->>MO: Create FCP Observer Note over MO: Watches for first<br/>contentful paint Tracker->>MO: Create LCP Observer Note over MO: Watches for largest<br/>contentful paint Tracker->>PO: Create CLS Observer Note over PO: Watches for layout<br/>shift entries Tracker->>PO: Create INP Observer Note over PO: Watches for interaction<br/>event entries par Vitals Detection MO-->>Tracker: FCP detected Note over Tracker: vitals.fcp = {<br/>value, loadState<br/>} MO-->>Tracker: LCP detected (updates on larger elements) Note over Tracker: vitals.lcp = {<br/>value, size, elTag,<br/>eid, elUrl<br/>} PO-->>Tracker: CLS shift detected Note over Tracker: vitals.cls = {<br/>value, largestShiftValue,<br/>largestShiftTime,<br/>largestShiftTarget, loadState<br/>} PO-->>Tracker: INP interaction detected Note over Tracker: vitals.inp = {<br/>value, interactionTarget,<br/>interactionTime, interactionType,<br/>inputDelay, processingDuration,<br/>presentationDelay,<br/>nextPaintTime, loadState<br/>} end App->>API: mfe.deregister() API->>Tracker: Get vitals Tracker->>API: Return vitals object API->>Events: Send MicroFrontEndTiming event Note over Events: Vitals sent as JSON strings:<br/>"nr.vitals.fcp": "{...}"<br/>"nr.vitals.lcp": "{...}"<br/>"nr.vitals.cls": "{...}"<br/>"nr.vitals.inp": "{...}"Testing
Unit Tests (
tests/unit/common/v2/mfe-vitals.test.js)E2E Tests (
tests/specs/api/register/mfe-vitals.e2e.js)Browser Compatibility:
Manual Testing Checklist:
nr.vitals.*JSON objects