From bfb3a21da6ef553b1587cbdfcd14266bb02dabe2 Mon Sep 17 00:00:00 2001 From: Michal Mocny Date: Tue, 7 Nov 2023 22:19:52 -0500 Subject: [PATCH] Refactor to be a lot more idiomatic --- sandbox/web-mightals.js/index.js | 12 ++++- sandbox/web-mightals.js/lib/cls.js | 47 ++++++++----------- sandbox/web-mightals.js/lib/inp.js | 52 +++++++-------------- sandbox/web-mightals.js/lib/interactions.js | 22 +++++++++ sandbox/web-mightals.js/lib/lcp.js | 29 ++++-------- sandbox/web-mightals.js/lib/loafs.js | 27 ++++------- sandbox/web-mightals.js/lib/pageSlicer.js | 13 ++++-- sandbox/web-mightals.js/lib/webMightals.js | 30 ++++++++++-- 8 files changed, 124 insertions(+), 108 deletions(-) create mode 100644 sandbox/web-mightals.js/lib/interactions.js diff --git a/sandbox/web-mightals.js/index.js b/sandbox/web-mightals.js/index.js index 4133288..6bdc895 100644 --- a/sandbox/web-mightals.js/index.js +++ b/sandbox/web-mightals.js/index.js @@ -1,3 +1,4 @@ +import pageSlicer from "./lib/pageSlicer"; import webMightals from "./lib/webMightals"; function block(ms) { @@ -11,9 +12,18 @@ myButton.addEventListener('click', (event) => { block(Math.random() * 400); }); +const pages = pageSlicer().subscribe((url) => { + console.log('Navigate', url); + +}); + const obs = webMightals().subscribe({ next: (value) => { - console.log(value); + console.group('webMightals'); + for (let [k,v] of Object.entries(value)) { + console.log(k, +v.score.toFixed(5), { entries: v.entries }); + } + console.groupEnd(); }, complete: () => { console.log('done'); diff --git a/sandbox/web-mightals.js/lib/cls.js b/sandbox/web-mightals.js/lib/cls.js index 64a2e66..6e2d6a5 100644 --- a/sandbox/web-mightals.js/lib/cls.js +++ b/sandbox/web-mightals.js/lib/cls.js @@ -1,34 +1,25 @@ -import { Observable, mergeMap } from "rxjs"; +import { Observable, mergeMap, scan } from "rxjs"; import fromPerformanceObserver from "./fromPerformanceObserver"; +// TODO: refactor to expose all shifts first, then convert to CLS export function cls() { - return new Observable(subscriber => { - let totalCls = 0; - const entries = []; - - const obs = fromPerformanceObserver({ - type: 'layout-shift', - buffered: true, - }).pipe( - mergeMap( - list => list.getEntries() - ) - ); - - obs.subscribe((entry) => { - if (!entry.hadRecentInput) { - entries.push(entry); - totalCls += entry.value; - subscriber.next({ - score: totalCls, - entries, - }); - } - }); - - // TODO: test this - return obs.unsubscribe; - }); + return fromPerformanceObserver({ + type: 'layout-shift', + // buffered: true, + }).pipe( + mergeMap( + list => list.getEntries() + .filter(entry => !entry.hadRecentInput) + .map(entry => ({ + score: entry.value, + entries: [entry], + })) + ), + scan((acc, curr) => ({ + score: acc.score + curr.score, + entries: acc.entries.concat(curr.entries), + }), { score: 0, entries: [] }) + ); } export default cls; \ No newline at end of file diff --git a/sandbox/web-mightals.js/lib/inp.js b/sandbox/web-mightals.js/lib/inp.js index 9885386..32bc24d 100644 --- a/sandbox/web-mightals.js/lib/inp.js +++ b/sandbox/web-mightals.js/lib/inp.js @@ -1,42 +1,26 @@ -import { Observable, filter, mergeAll, mergeMap } from "rxjs"; +import { Observable, distinctUntilChanged, filter, groupBy, map, mergeAll, mergeMap, scan } from "rxjs"; import fromPerformanceObserver from "./fromPerformanceObserver"; +import interactions from "./interactions"; export function inp() { - return new Observable(subscriber => { - const idToEventMap = {}; - let maxInp = 0; + let maxInp = 0; - const obs = fromPerformanceObserver({ - type: 'event', - buffered: true, - durationThreshold: 0, - }).pipe( - mergeMap( - list => list.getEntries() - ), - filter( - et => !!et.interactionId - ) + return interactions() + .pipe( + groupBy((value) => value.entries[0].interactionId), + mergeMap((group$) => { + return group$.pipe( + scan((acc, curr) => ({ + score: Math.max(acc.score, curr.score), + entries: acc.entries.concat(curr.entries), + }), { score: 0, entries: [] }) + ); + }), + distinctUntilChanged((prev, curr) => { + // Return true if the current INP is "same" as existing + return curr.score <= prev.score; + }) ); - - obs.subscribe((entry) => { - const interactionEntries = (idToEventMap[entry.interactionId] ??= []); - interactionEntries.push(entry); - if (entry.duration > maxInp) { - maxInp = entry.duration; - subscriber.next({ - score: entry.duration, - entries: interactionEntries, - }); - } - }); - - // TODO: test this - return () => { - console.log('here'); - obs.unsubscribe(); - }; - }); } export default inp; \ No newline at end of file diff --git a/sandbox/web-mightals.js/lib/interactions.js b/sandbox/web-mightals.js/lib/interactions.js new file mode 100644 index 0000000..af3b60a --- /dev/null +++ b/sandbox/web-mightals.js/lib/interactions.js @@ -0,0 +1,22 @@ +import { Observable, filter, map, mergeAll, mergeMap } from "rxjs"; +import fromPerformanceObserver from "./fromPerformanceObserver"; + +// TODO: Should this perhaps group interaction events together? +export function interactions() { + return fromPerformanceObserver({ + type: 'event', + // buffered: true, + durationThreshold: 0, + }).pipe( + mergeMap( + list => list.getEntries() + .filter(entry => entry.interactionId != 0) + .map(entry => ({ + score: entry.duration, + entries: [entry], + })) + ) + ); +} + +export default interactions; \ No newline at end of file diff --git a/sandbox/web-mightals.js/lib/lcp.js b/sandbox/web-mightals.js/lib/lcp.js index 820cb62..9889789 100644 --- a/sandbox/web-mightals.js/lib/lcp.js +++ b/sandbox/web-mightals.js/lib/lcp.js @@ -1,27 +1,18 @@ -import { Observable, mergeMap } from "rxjs"; +import { mergeMap } from "rxjs"; import fromPerformanceObserver from "./fromPerformanceObserver"; export function lcp() { - return new Observable(subscriber => { - const obs = fromPerformanceObserver({ - type: 'largest-contentful-paint', - buffered: true, - }).pipe( - mergeMap( - list => list.getEntries() - ) - ); - - obs.subscribe((entry) => { - subscriber.next({ + return fromPerformanceObserver({ + type: 'largest-contentful-paint', + buffered: true, + }).pipe( + mergeMap( + list => list.getEntries().map(entry => ({ score: entry.startTime, entries: [entry], - }); - }); - - // TODO: test this - return obs.unsubscribe; - }); + })) + ) + ); } export default lcp; \ No newline at end of file diff --git a/sandbox/web-mightals.js/lib/loafs.js b/sandbox/web-mightals.js/lib/loafs.js index 18ac8c4..8576a36 100644 --- a/sandbox/web-mightals.js/lib/loafs.js +++ b/sandbox/web-mightals.js/lib/loafs.js @@ -2,26 +2,17 @@ import { Observable, mergeMap } from "rxjs"; import fromPerformanceObserver from "./fromPerformanceObserver"; export function loafs() { - return new Observable(subscriber => { - const obs = fromPerformanceObserver({ - type: 'long-animation-frame', - buffered: true, - }).pipe( - mergeMap( - list => list.getEntries() - ) - ); - - obs.subscribe((entry) => { - subscriber.next({ + return fromPerformanceObserver({ + type: 'long-animation-frame', + // buffered: true, + }).pipe( + mergeMap( + list => list.getEntries().map(entry => ({ score: entry.duration, entries: [entry], - }); - }); - - // TODO: test this - return obs.unsubscribe; - }); + })) + ) + ); } export default loafs; \ No newline at end of file diff --git a/sandbox/web-mightals.js/lib/pageSlicer.js b/sandbox/web-mightals.js/lib/pageSlicer.js index dff0677..bd9a76b 100644 --- a/sandbox/web-mightals.js/lib/pageSlicer.js +++ b/sandbox/web-mightals.js/lib/pageSlicer.js @@ -13,13 +13,20 @@ import { Observable } from 'rxjs'; export function pageSlicer() { return new Observable(subscriber => { - const value = undefined; - subscriber.next(value); + navigation.addEventListener("navigate", e => { + console.log(e); + + if (!e.canIntercept || e.hashChange) { + return; + } + subscriber.next(e.destination.url); + }); + + // TODO: remove event listener? const cleanup = () => {}; return cleanup; }); - } export default pageSlicer; \ No newline at end of file diff --git a/sandbox/web-mightals.js/lib/webMightals.js b/sandbox/web-mightals.js/lib/webMightals.js index b64d849..554804b 100644 --- a/sandbox/web-mightals.js/lib/webMightals.js +++ b/sandbox/web-mightals.js/lib/webMightals.js @@ -4,21 +4,41 @@ * - Each new entry added to correct slice first */ -import { combineLatest, startWith } from "rxjs"; +import { combineLatest, debounceTime, startWith } from "rxjs"; import inp from "./inp"; import cls from "./cls"; import lcp from "./lcp"; +import loafs from "./loafs"; +import interactions from "./interactions"; // TODO: subscribe with next: and complete: to take all vs only final scores? // TODO: can complete() handler take the last value? export function webMightals() { const mightals = { - "inp": inp().pipe(startWith({ score: 0, entries: [] })), - "cls": cls().pipe(startWith({ score: 0, entries: [] })), - "lcp": lcp().pipe(startWith({ score: 0, entries: [] })), + "inp": inp(), + "cls": cls(), + "lcp": lcp(), + + // "interactions": interactions(), + "loafs": loafs(), }; - return combineLatest(mightals); + + return combineLatest( + Object.fromEntries( + Object.entries(mightals).map( + ([k,v]) => [ + k, + v.pipe( + startWith({ score: 0, entries: [] }) + ) + ] + ) + ) + ).pipe( + debounceTime(0) + ); + } export default webMightals; \ No newline at end of file