{"id":13656780,"url":"https://github.com/treosh/uxm","last_synced_at":"2025-04-05T15:10:09.526Z","repository":{"id":31892831,"uuid":"109379252","full_name":"treosh/uxm","owner":"treosh","description":"A modular library for collecting front-end performance metrics.","archived":false,"fork":false,"pushed_at":"2022-11-09T09:27:15.000Z","size":953,"stargazers_count":247,"open_issues_count":1,"forks_count":11,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-29T14:09:54.345Z","etag":null,"topics":["real-user-monitoring","user-centric-metrics","user-experience","web-performance"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/treosh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-11-03T09:49:36.000Z","updated_at":"2024-07-10T18:30:52.000Z","dependencies_parsed_at":"2022-07-28T12:18:59.830Z","dependency_job_id":null,"html_url":"https://github.com/treosh/uxm","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/treosh%2Fuxm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/treosh%2Fuxm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/treosh%2Fuxm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/treosh%2Fuxm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/treosh","download_url":"https://codeload.github.com/treosh/uxm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247353749,"owners_count":20925329,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["real-user-monitoring","user-centric-metrics","user-experience","web-performance"],"created_at":"2024-08-02T05:00:32.189Z","updated_at":"2025-04-05T15:10:09.502Z","avatar_url":"https://github.com/treosh.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./.github/logo.png\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  An utility library for collecting user-centric performance metrics.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#\"\u003eWhy?\u003c/a\u003e • \u003ca href=\"#usage\"\u003eUsage\u003c/a\u003e • \u003ca href=\"#api\"\u003eAPI\u003c/a\u003e • \u003ca href=\"#credits\"\u003eCredits\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n**Features**:\n\n- Modular design based on ES modules.\n- Small size (2.5kb gzip). It's usually smaller when you use a few features and [Tree Shaking](https://webpack.js.org/guides/tree-shaking/).\n- Graceful support of latest browser APIs like [Performance Paint Timing](https://developer.mozilla.org/en-US/docs/Web/API/PerformancePaintTiming), [Network Information](https://wicg.github.io/netinfo/), or [Device Memory](https://w3c.github.io/device-memory/).\n- Fully featured [User Timing API v3](https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API) support.\n\n## Usage\n\n[![](https://img.shields.io/npm/v/uxm.svg)](https://npmjs.org/package/uxm)\n[![](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n\n```bash\nnpm install uxm@next\n```\n\nCollect [user-centric metrics](https://web.dev/metrics/) and send data to your API (1.5Kb):\n\n```js\nimport { collectMetrics, createApiReporter, getDeviceInfo } from 'uxm'\n\nconst report = createApiReporter('/api/collect', { initial: getDeviceInfo() })\n\ncollectMetrics(['fcp', 'lcp', 'fid', 'cls'], ({ metricType, value }) =\u003e {\n  report({ [metricType]: value })\n})\n```\n\nAt the end of the session (on `visibilitychange` event), your API receives a POST request (using `sendBeacon`) with data for core UX metrics and a device information, like:\n\n```json\n{\n  \"fcp\": 1409,\n  \"fid\": 64,\n  \"lcp\": 2690,\n  \"cls\": 0.025,\n  \"url\": \"https://example.com/\",\n  \"memory\": 8,\n  \"cpus\": 2,\n  \"connection\": { \"effectiveType\": \"4g\", \"rtt\": 150, \"downlink\": 4.25 }\n}\n```\n\nExplore examples for building a robust real-user monitoring (RUM) logic. Size of each example is controlled using [size-limit](./package.json#L74).\n\n\u003cdetails\u003e\n  \u003csummary\u003eReport FCP and FID to Google Analytics (0.7 KB)\u003c/summary\u003e\n\nUse Google Analytics as a free RUM service, and report user-centric performance metrics.\nLearn more about [using Google Analytics for site speed monitoring](https://philipwalton.com/articles/the-google-analytics-setup-i-use-on-every-site-i-build/#performance-tracking).\n\n[`google-analytics-reporter.js`](./examples/google-analytics-reporter.js):\n\n```js\nimport { collectFcp, collectFid } from 'uxm'\n\ncollectFcp(reportToGoogleAnalytics)\ncollectFid(reportToGoogleAnalytics)\n\nfunction reportToGoogleAnalytics(metric) {\n  ga('send', 'event', {\n    eventCategory: 'Performance Metrics',\n    eventAction: 'track',\n    [metric.metricType]: metric.value,\n  })\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eMeasure React view render performance (0.65 KB)\u003c/summary\u003e\n\nA react-hook example that measures rendering performance and creates a custom [user-timing](https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API) measure.\n\n[`react-use-time-hook.js`](./examples/react-use-time-hook.js):\n\n```js\nimport { time, timeEndPaint } from 'uxm'\n\nexport function App() {\n  useTime('render:app')\n  return 'Hello from React'\n}\n\nfunction useTime(label) {\n  time(label) // render started\n  useEffect(() =\u003e timeEndPaint(label), []) // render ended, and the browser paint has been procceed.\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eBuild a custom layout-shift metric for SPA (0.8 KB)\u003c/summary\u003e\n\n[Layout Instability](https://wicg.github.io/layout-instability/) is a flexible API that allows building custom metrics on top — like, measuring cumulative layout shift per view, not the whole session.\n\n[`custom-layout-shift.js`](./examples/custom-layout-shift.js):\n\n```js\nimport { observeEntries } from 'uxm'\nimport { observeHistory } from 'uxm/experimental'\n\n/** @type {{ url: string, cls: number }[]} */\nlet views = []\nlet cls = 0\n\n// cummulate `layout-shift` values, with an input\n\nobserveEntries('layout-shift', (layoutShiftEntries) =\u003e {\n  layoutShiftEntries.forEach((e) =\u003e {\n    if (!e.hadRecentInput) cls += e.value\n  })\n})\n\n// observe `history` changes,\n// and reset `cls` when a route changes\n\nobserveHistory((e) =\u003e {\n  views.push({ url: e.prevUrl, cls })\n  cls = 0\n})\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eCollect CrUX-like metrics (1.6Kb)\u003c/summary\u003e\n\n[Chrome UX Report (CrUX)](https://developers.google.com/web/tools/chrome-user-experience-report/) is a great way to see\nhow real-world Chrome users experience the speed of your website. But for privacy reasons, CrUX aggregates data only per origin.\n\nThis script collects detailed crux-like analytics on the URL level.\n\n[`crux-metrics.js`](./examples/crux-metrics.js):\n\n```js\nimport { getDeviceInfo, collectLoad, collectFcp, collectLcp, collectFid, collectCls, onVisibilityChange } from 'uxm'\n\n// init `metrics` and get device information\n\nconst { connection, url } = getDeviceInfo()\nconst metrics = { url, effectiveConnectionType: connection.effectiveType }\n\n// collect loading metrics\n\ncollectLoad(({ value: load, detail: { domContentLoaded, timeToFirstByte } }) =\u003e {\n  metrics.timeToFirstByte = timeToFirstByte\n  metrics.domContentLoaded = domContentLoaded\n  metrics.load = load\n})\n\n// collect user-centric metrics\n\ncollectFcp(({ value }) =\u003e (metrics.firstContentfulPaint = value))\ncollectLcp(({ value }) =\u003e (metrics.largestContentfulPaint = value))\ncollectFid(({ value }) =\u003e (metrics.firstInputDelay = value))\ncollectCls(({ value }) =\u003e (metrics.cumulativeLayoutShift = value))\n\n// all metrics are collected on \"visibilitychange\" event\n\nonVisibilityChange(() =\u003e {\n  console.log(metrics)\n  //  {\n  //    \"url\": \"https://example.com/\",\n  //    \"effectiveConnectionType\": \"4g\",\n  //    \"timeToFirstByte\": 1204,\n  //    \"domContentLoaded\": 1698,\n  //    \"load\": 2508\n  //    \"firstContentfulPaint\": 1646,\n  //    \"largestContentfulPaint\": 3420,\n  //    \"firstInputDelay\": 12,\n  //    \"cumulativeLayoutShift\": 0.12,\n  //  }\n}, 1)\n```\n\n\u003c/details\u003e\n\n## API\n\n- [Metrics](#metrics)\n  - [collectMetrics(metrics, callback)](#collectmetricsmetrics-callback)\n  - [collectFcp(callback)](#collectfcpcallback)\n  - [collectFid(callback)](#collectfidcallback)\n  - [collectLcp(callback, [options])](#collectlcpcallback-options)\n  - [collectCls(callback, [options])](#collectclscallback-options)\n  - [collectLoad(callback)](#collectloadcallback)\n- [Performance Observer](#performance-observer)\n  - [observeEntries(options, callback)](#)\n  - [getEntriesByType(entryType)](#)\n  - [onVisibilityChange(callback)](#)\n  - [onLoad(callback)](#)\n- [Reporter](#reporter)\n  - [createApiReporter(url, [options])](#)\n  - [getDeviceInfo()](#)\n- [User-timing](#user-timing)\n  - [mark(markName, [markOptions])](#)\n  - [measure(markName, [startOrMeasureOptions], [endMarkName])](#)\n  - [time(label, [startLabel])](#)\n  - [timeEnd(label, [startLabel])](#)\n  - [timeEndPaint(label, [startLabel])](#)\n  - [now()](#)\n- [Experimental (`alpha`)](#experimental-alpha)\n  - [collectCid(callback)](#)\n  - [observeHistory(callback)](#)\n  - [recordTrace(callback, [options])](#)\n  - [calcSpeedScore(values, [ranks])](#)\n\n### Metrics\n\nMetrics are the core of `uxm` (`uxm` is a 3-letter acronym that stands for User eXperience Metrics).\n\nIt focuses on metrics, that captures a user experience, instead of measuring technical details, that are easy to manipulate.\nThis metrics are more representetive for a user, and the final purpose of a good frontend is to create a delightful user experience.\n\nEach metric follows the structure:\n\n- `metricType` \u003c[string]\u003e - a metric acronym, ex: `lcp`, `fid`, or `cls`.\n- `value` \u003c[number]\u003e - a numeric value of a metric, ex: `1804` for `lcp`, `4` for `fid`, or `0.129` for `cls`.\n- `detail` \u003c[object]\u003e - an extra detail specific for an each metric, like `elementSelector` for `lcp`, event `name` for `fid`, or `totalEntries` for `cls`.\n\nwith an exception for `collectLoad` (it does not have a 3-letters acronym, and considered a legacy.)\nUse a per-metric function for more granular control of the callback behavior and saving a bundle size.\n\nThis metrics are only available in Chromium-based browsers (Chrome, Edge, Opera).\n\nThe best way to understand a metric is to read web.dev/metrics and check [the source](./src/metrics.js).\n\n#### collectMetrics(metrics, callback)\n\n- `metrics` \u003c[array]\u003c[string]|[object]\u003e\u003e\n- `callback` \u003c[function]\u003e\n\nThe method is a shortcut for calling [`collectFcp`](#collectfcpcallback), [`collectFid`](#collectfidcallback), [`collectLcp`](#collectlcpcallback-options), and [`collectCls`](#collectclscallback-options).\n\n```js\nimport { collectMetrics } from 'uxm'\n\nconst report = createApiReporter('/api/collect')\n\n// pass a metric 3-letter acronym\ncollectMetrics(['fcp', 'fid'], (metric) =\u003e {\n  report({ [metric.metricType]: metric.value })\n})\n\n// or a metric options using an object and `type`\ncollectMetrics([{ type: 'lcp', maxTimeout: 1000 }], (metric) =\u003e {\n  report({ lcp: metric.value })\n})\n```\n\n#### collectFcp(callback)\n\n- `callback` \u003c[function]\u003e a callback with FcpMetric:\n  - `metricType` \u003c`\"fcp\"`\u003e\n  - `value` \u003c[number]\u003e a time when the user can see anything on the screen – a fast FCP helps reassure the user that something is **happening**.\n\nCollect [First Contentful Paint (FCP)](https://web.dev/fcp/) using [`paint`](https://www.w3.org/TR/paint-timing/) entries.\n\n#### collectFid(callback)\n\n- `callback` \u003c[function]\u003e a callback with `FidMetric`:\n  - `metricType` \u003c`\"fid\"`\u003e\n  - `value` \u003c[number]\u003e\n  - `detail` \u003c[object]\u003e\n    - `name` \u003c[string]\u003e\n    - `duration` \u003c[number]\u003e\n    - `startTime` \u003c[number]\u003e\n    - `processingStart` \u003c[number]\u003e\n    - `processingEnd` \u003c[number]\u003e\n\n```js\nimport { collectFid } from 'uxm'\n\ncollectFid((metric) =\u003e {\n  console.log(metric)\n  // { metricType: \"fid\", value: 1, detail: { duration: 8, startTime: 2568.1, processingStart: 2568.99, processingEnd: 2569.02, name: \"mousedown\" }\n})\n```\n\n#### collectLcp(callback, [options])\n\n- `callback` \u003c[function]\u003e a callback with `LcpMetric`:\n  - `metricType` \u003c`\"lcp\"`\u003e\n  - `value` \u003c[number]\u003e a time when the page's main content has likely loaded – a fast LCP helps reassure the user that the page is **useful**.\n  - `detail` \u003c[object]\u003e\n    - `elementSelector` \u003c[string]\u003e CSS selector of an element, that is triggered the most significant paint\n    - `size` \u003c[number]\u003e size (`height` x `width`) of the largest element\n- `options` \u003c[object]\u003e (Optional)\n  - `maxTimeout` \u003c[number]\u003e The longest delay between `largest-contentful-paint` entries to consider the LCP. Defaults to `10000` ms.\n\nCollect [Largest Contentful Paint (LCP)](https://web.dev/lcp/) using [`largest-contentful-paint`](https://wicg.github.io/largest-contentful-paint/) entries.\nA callback triggers when a user interacts with a page, or after `maxTimeout` between entries, or on `\"visibilitychange\"` event.\n\n```js\nimport { collectLcp } from 'uxm'\n\ncollectLcp((metric) =\u003e {\n  console.log(metric) // { metricType: \"lcp\", value: 2450, detail: { size: 8620, elementSelector: \"body \u003e h1\" } }\n})\n```\n\n#### collectCls(callback, [options])\n\n- `callback` \u003c[function]\u003e a callback with `ClsMetric`:\n  - `metricType` \u003c`\"cls\"`\u003e\n  - `value` \u003c[number]\u003e\n  - `detail` \u003c[object]\u003e\n    - `totalEntries` \u003c[number]\u003e\n    - `sessionDuration` \u003c[number]\u003e\n\n```js\nimport { collectCls } from 'uxm'\n\ncollectCls(\n  (metric) =\u003e {\n    console.log(metric) // { metricType: \"cls\", value: 0.0893, detail: { totalEntries: 2, sessionDuration: 2417 } }\n  },\n  { maxTimeout: 1000 }\n)\n```\n\n#### collectLoad(callback)\n\n- `callback` \u003c[function]\u003e a callback with `ClsMetric`:\n  - `metricType` \u003c`\"load\"`\u003e\n  - `value` \u003c[number]\u003e\n  - `detail` \u003c[object]\u003e\n    - `timeToFirstByte` \u003c[number]\u003e\n    - `domContentLoaded` \u003c[number]\u003e\n\n```js\nimport { collectLoad } from 'uxm'\n\ncollectLoad(({ value: load, detail: { domContentLoaded, timeToFirstByte } }) =\u003e {\n  console.log({ timeToFirstByte, domContentLoaded, load })\n})\n```\n\n### Performance Observer\n\n#### observeEntries(options, callback)\n\n#### getEntriesByType(entryType)\n\n### Reporter\n\n#### createApiReporter(url, [options])\n\n### User Timing\n\n#### mark(markName, [markOptions])\n\n#### measure(markName, [startOrMeasureOptions], [endMarkName])\n\n#### time(label, [startLabel])\n\n#### timeEnd(label, [startLabel])\n\n#### timeEndPaint(label, [startLabel])\n\n#### now()\n\n### Device Info\n\n#### getDeviceInfo()\n\n### Experimental (`alpha`)\n\n#### collectCid(callback)\n\n#### observeHistory(callback)\n\n#### recordTrace(callback, [options])\n\n#### calcSpeedScore(values, [ranks])\n\n---\n\n### Credits\n\nMade with ❤️ by [Treo](https://treo.sh/).\n\n[array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array 'Array'\n[function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function 'Function'\n[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type 'Number'\n[object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object 'Object'\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftreosh%2Fuxm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftreosh%2Fuxm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftreosh%2Fuxm/lists"}