Closed staabm closed 4 years ago
Maybe we could calculate a number similar to https://googlechrome.github.io/lighthouse/scorecalc/
web-vitals-reporter@0.2.0
adds beforeSend
option, which you can use to customize final output. Use logic from lighthouse-plugin-field-performance and Lighthouse's computeLogNormal to get the final score.
Working example:
import { getCLS, getFID, getLCP } from 'web-vitals'
import { createApiReporter } from 'web-vitals-reporter'
const report = createApiReporter('/analytics', {
beforeSend: (result) => {
const lcpScore = computeLogNormalScore({ p10: 2500, median: 4500 }, result.LCP)
const fidScore = computeLogNormalScore({ p10: 100, median: 300 }, result.FID || 0)
const clsScore = computeLogNormalScore({ p10: 0.1, median: 0.25 }, result.CLS)
const score = Math.min(lcpScore, fidScore, clsScore)
return { lcpScore, fidScore, clsScore, score }
},
})
getLCP(report)
getFID(report)
getCLS(report)
// helpers extracted from Lighthouse
// https://github.com/GoogleChrome/lighthouse/blob/b36b694f12b668cc64d232b8fe7c0507011bfdb7/lighthouse-core/audits/audit.js#L79
/** @param {{median: number, p10: number}} controlPoints @param {number} value */
function computeLogNormalScore(controlPoints, value) {
const percentile = getLogNormalScore(controlPoints, value)
return clampTo2Decimals(percentile)
}
/** @param {{median: number, p10: number}} parameters @param {number} value */
function getLogNormalScore({ median, p10 }, value) {
// Required for the log-normal distribution.
if (median <= 0) throw new Error('median must be greater than zero')
if (p10 <= 0) throw new Error('p10 must be greater than zero')
// Not required, but if p10 > median, it flips around and becomes the p90 point.
if (p10 >= median) throw new Error('p10 must be less than the median')
// Non-positive values aren't in the distribution, so always 1.
if (value <= 0) return 1
// Closest double to `erfc-1(2 * 1/10)`.
const INVERSE_ERFC_ONE_FIFTH = 0.9061938024368232
// Shape (σ) is `log(p10/median) / (sqrt(2)*erfc^-1(2 * 1/10))` and
// standardizedX is `1/2 erfc(log(value/median) / (sqrt(2)*σ))`, so simplify a bit.
const xLogRatio = Math.log(value / median)
const p10LogRatio = -Math.log(p10 / median) // negate to keep σ positive.
const standardizedX = (xLogRatio * INVERSE_ERFC_ONE_FIFTH) / p10LogRatio
const complementaryPercentile = (1 - erf(standardizedX)) / 2
// Clamp to [0, 1] to avoid any floating-point out-of-bounds issues.
return Math.min(1, Math.max(0, complementaryPercentile))
}
/** @param {number} x */
function erf(x) {
// erf(-x) = -erf(x);
const sign = Math.sign(x)
x = Math.abs(x)
const a1 = 0.254829592
const a2 = -0.284496736
const a3 = 1.421413741
const a4 = -1.453152027
const a5 = 1.061405429
const p = 0.3275911
const t = 1 / (1 + p * x)
const y = t * (a1 + t * (a2 + t * (a3 + t * (a4 + t * a5))))
return sign * (1 - y * Math.exp(-x * x))
}
/** @param {number} val */
function clampTo2Decimals(val) {
return Math.round(val * 100) / 100
}
Would it be possible for this lib to compute a overall score (like the lighthouse perf score)?
I want to integrate the low level metrics into a cms, but it would help for a top level view if there was a single score at first. The user would then „drill down“ into the separate metrics