web-vitals-element
Version:
> Bring [web vitals](https://github.com/GoogleChrome/web-vitals) quickly into your page using custom elements
244 lines (218 loc) • 11.2 kB
JavaScript
var e,t,n,i,a=function(e,t){return {name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},r=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},o=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)));};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0);},u=function(e){addEventListener("pageshow",(function(t){t.persisted&&e(t);}),!0);},c="function"==typeof WeakSet?new WeakSet:new Set,f=function(e,t,n){var i;return function(){t.value>=0&&(n||c.has(t)||"hidden"===document.visibilityState)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)));}},s=-1,m=function(){return "hidden"===document.visibilityState?0:1/0},d=function(){o((function(e){var t=e.timeStamp;s=t;}),!0);},v=function(){return s<0&&(s=m(),d(),u((function(){setTimeout((function(){s=m(),d();}),0);}))),{get firstHiddenTime(){return s}}},p=function(e,t){var n,i=v(),o=a("FCP"),s=function(e){"first-contentful-paint"===e.name&&(d&&d.disconnect(),e.startTime<i.firstHiddenTime&&(o.value=e.startTime,o.entries.push(e),c.add(o),n()));},m=performance.getEntriesByName&&performance.getEntriesByName("first-contentful-paint")[0],d=m?null:r("paint",s);(m||d)&&(n=f(e,o,t),m&&s(m),u((function(i){o=a("FCP"),n=f(e,o,t),requestAnimationFrame((function(){requestAnimationFrame((function(){o.value=performance.now()-i.timeStamp,c.add(o),n();}));}));})));},l=!1,h=-1,y=function(e,t){l||(p((function(e){h=e.value;})),l=!0);var n,i=function(t){h>-1&&e(t);},c=a("CLS",0),s=0,m=[],d=function(e){if(!e.hadRecentInput){var t=m[0],i=m[m.length-1];s&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(s+=e.value,m.push(e)):(s=e.value,m=[e]),s>c.value&&(c.value=s,c.entries=m,n());}},v=r("layout-shift",d);v&&(n=f(i,c,t),o((function(){v.takeRecords().map(d),n();})),u((function(){s=0,h=-1,c=a("CLS",0),n=f(i,c,t);})));},g={passive:!0,capture:!0},T=new Date,E=function(i,a){e||(e=a,t=i,n=new Date,L(removeEventListener),S());},S=function(){if(t>=0&&t<n-T){var a={entryType:"first-input",name:e.type,target:e.target,cancelable:e.cancelable,startTime:e.timeStamp,processingStart:e.timeStamp+t};i.forEach((function(e){e(a);})),i=[];}},w=function(e){if(e.cancelable){var t=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){E(e,t),a();},i=function(){a();},a=function(){removeEventListener("pointerup",n,g),removeEventListener("pointercancel",i,g);};addEventListener("pointerup",n,g),addEventListener("pointercancel",i,g);}(t,e):E(t,e);}},L=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,w,g)}));},b=function(n,s){var m,d=v(),p=a("FID"),l=function(e){e.startTime<d.firstHiddenTime&&(p.value=e.processingStart-e.startTime,p.entries.push(e),c.add(p),m());},h=r("first-input",l);m=f(n,p,s),h&&o((function(){h.takeRecords().map(l),h.disconnect();}),!0),h&&u((function(){var r;p=a("FID"),m=f(n,p,s),i=[],t=-1,e=null,L(addEventListener),r=l,i.push(r),S();}));},F=function(e,t){var n,i=v(),s=a("LCP"),m=function(e){var t=e.startTime;t<i.firstHiddenTime&&(s.value=t,s.entries.push(e)),n();},d=r("largest-contentful-paint",m);if(d){n=f(e,s,t);var p=function(){c.has(s)||(d.takeRecords().map(m),d.disconnect(),c.add(s),n());};["keydown","click"].forEach((function(e){addEventListener(e,p,{once:!0,capture:!0});})),o(p,!0),u((function(i){s=a("LCP"),n=f(e,s,t),requestAnimationFrame((function(){requestAnimationFrame((function(){s.value=performance.now()-i.timeStamp,c.add(s),n();}));}));}));}},k=function(e){var t,n=a("TTFB");t=function(){try{var t=performance.getEntriesByType("navigation")[0]||function(){var e=performance.timing,t={entryType:"navigation",startTime:0};for(var n in e)"navigationStart"!==n&&"toJSON"!==n&&(t[n]=Math.max(e[n]-e.navigationStart,0));return t}();if(n.value=n.delta=t.responseStart,n.value<0)return;n.entries=[t],e(n);}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("pageshow",t);};
var webVitals = /*#__PURE__*/Object.freeze({
__proto__: null,
getCLS: y,
getFCP: p,
getFID: b,
getLCP: F,
getTTFB: k
});
const MS_UNIT = 'ms';
// borrowed from the vitals extension
// https://github.com/GoogleChrome/web-vitals-extension/blob/master/src/browser_action/vitals.js#L20-L23
const METRIC_CONFIG = new Map([
[
'CLS',
{
thresholds: {
good: 0.1,
needsImprovement: 0.25,
},
observerEntryType: 'layout-shift',
explainerURL: 'https://web.dev/cls/',
longName: 'Cumulative Layout Shift',
roundFn: (value) => Math.floor(value * 100) / 100,
},
],
[
'FCP',
{
thresholds: {
good: 2500,
},
observerEntryType: 'paint',
explainerURL: 'https://web.dev/fcp/',
unit: MS_UNIT,
longName: 'First Contentful Paint',
},
],
[
'FID',
{
thresholds: {
good: 100,
needsImprovement: 300,
},
observerEntryType: 'first-input',
explainerURL: 'https://web.dev/fid/',
unit: MS_UNIT,
longName: 'First Input Delay',
},
],
[
'LCP',
{
thresholds: {
good: 2500,
needsImprovement: 4000,
},
observerEntryType: 'paint',
explainerURL: 'https://web.dev/lcp/',
unit: MS_UNIT,
longName: 'Largest Contentful Paint',
},
],
[
'TTFB',
{
thresholds: {
good: 2500,
},
explainerURL: 'https://web.dev/time-to-first-byte/',
unit: MS_UNIT,
longName: 'Time to first byte',
},
],
]);
const GENERAL_ATTRIBUTES = ['class', 'style'];
const CONFIG_ATTRIBUTES = ['show-unsupported', 'show-metric-name'];
class WebVitals extends HTMLElement {
constructor() {
super();
this.unsupportedMetrics = [];
this.metrics = new Map();
}
connectedCallback() {
const metricAttributes = this.getMetricAttributes();
const metricList = metricAttributes.length
? metricAttributes
: [...METRIC_CONFIG.keys()];
this.metrics = this.getMetrics(metricList);
this.render();
for (let metricConfig of this.metrics.values()) {
const { name, getWebVitalsValue } = metricConfig;
getWebVitalsValue((metric) => {
this.metrics.set(name, {
...metricConfig,
...metric,
});
this.render();
}, true);
}
}
getMetricAttributes() {
return this.getAttributeNames()
.filter(
(attr) =>
!GENERAL_ATTRIBUTES.includes(attr) &&
!CONFIG_ATTRIBUTES.includes(attr)
)
.map((attr) => attr.toUpperCase());
}
getMetrics(metricList) {
return new Map(
metricList.reduce((acc, metricName) => {
// exclude metric when it's not supported by web-vitals
const getWebVitalsValue = webVitals[`get${metricName}`];
if (!getWebVitalsValue) {
console.error(`${metricName} is not supported by '<web-vitals />'`);
this.unsupportedMetrics.push(metricName);
return acc;
}
// exclude metric when it's not supported
const metricConfig = METRIC_CONFIG.get(metricName);
const { observerEntryType } = metricConfig;
if (
observerEntryType &&
!PerformanceObserver.supportedEntryTypes.includes(observerEntryType)
) {
console.error(`${metricName} is not supported by your browser`);
this.unsupportedMetrics.push(metricName);
return acc;
}
return [
...acc,
[
metricName,
{
...METRIC_CONFIG.get(metricName),
getWebVitalsValue,
name: metricName,
},
],
];
}, [])
);
}
render() {
this.innerHTML = `<div class="web-vitals">
<dl>
${[...this.metrics]
.map(([key, metric]) => {
const { explainerURL, longName, roundFn, thresholds, unit, value } =
metric;
let classes = '';
const roundValue = roundFn || Math.floor;
const { good, needsImprovement } = thresholds;
if (value) {
classes += 'is-final ';
let score = 'is-poor';
if (needsImprovement && value <= needsImprovement) {
score = 'needs-improvement';
}
if (value <= good) {
score = 'is-good';
}
classes += score;
}
return `
<div class="${classes}">
<dt>
${
this.hasAttribute('show-metric-name')
? `${longName} (<a href="${explainerURL}">${key}</a>)`
: `<a href="${explainerURL}">${key}</a>`
}
</dt>
<dd>${
value ? `${roundValue(value)}${unit ? unit : ''}` : '...'
}</dd>
</div>
`;
})
.join('')}
</dl>
${
this.unsupportedMetrics.length &&
this.hasAttribute('show-unsupported')
? `<p>Not supported: ${this.unsupportedMetrics.join(', ')}</p>`
: ''
}
</div>`;
}
}
function styleInject(css, ref) {
if ( ref === void 0 ) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css_248z = ".web-vitals {\n max-width: 20em;\n border-radius: 0.5em;\n overflow: hidden;\n padding: 1em;\n font-family: Arial, Helvetica, sans-serif;\n box-shadow: 0 0.25em 0.35em rgba(23, 23, 23, 0.4);\n color: #444;\n background: #fff;\n}\n\n.web-vitals a {\n color: #444;\n font-weight: bold;\n}\n\n.web-vitals dl {\n margin: 0;\n grid-template-columns: 1fr 1fr;\n}\n\n.web-vitals p {\n margin: 0.5em 0 0;\n font-size: 0.75em;\n}\n\n.web-vitals div {\n display: grid;\n grid-template-columns: 3fr 1fr;\n}\n\n.web-vitals div dd::after {\n margin-left: 0.25em;\n line-height: 1;\n}\n\n.web-vitals div:not(.is-done) dd::after {\n content: '🕐';\n}\n\n.web-vitals div.is-good dd::after {\n content: '✅';\n}\n\n.web-vitals div.needs-improvement dd::after {\n content: '⚠️';\n}\n\n.web-vitals div.is-poor dd::after {\n content: '❌';\n}\n\n.web-vitals dd {\n text-align: right;\n margin-left: 1em;\n}\n";
styleInject(css_248z);
customElements.define('web-vitals', WebVitals);