Update app and tooling

This commit is contained in:
Lawrence Chen 2026-01-29 17:36:26 -08:00
parent 3046531bdd
commit e620ec7349
4950 changed files with 2975120 additions and 10 deletions

26
node_modules/web-vitals/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,26 @@
# Changelog
### v0.2.4 (2020-07-23)
- Remove the unload listener ([#68](https://github.com/GoogleChrome/web-vitals/pull/68))
### v0.2.3 (2020-06-26)
- Ensure reports only occur if a PO was created ([#58](https://github.com/GoogleChrome/web-vitals/pull/58))
### v0.2.2 (2020-05-12)
- Remove package `type` field ([#35](https://github.com/GoogleChrome/web-vitals/pull/35))
### v0.2.1 (2020-05-06)
- Ensure all modules are pure modules ([#23](https://github.com/GoogleChrome/web-vitals/pull/23))
- Ensure proper TypeScript exports and config ([#22](https://github.com/GoogleChrome/web-vitals/pull/22))
### v0.2.0 (2020-05-03)
- Initial public release
### v0.1.0 (2020-04-24)
- Initial pre-release

202
node_modules/web-vitals/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

423
node_modules/web-vitals/README.md generated vendored Normal file
View file

@ -0,0 +1,423 @@
# `web-vitals`
- [Overview](#overview)
- [Installation](#installation)
- [Usage](#usage)
- [Log the results to the console](#log-the-results-to-the-console)
- [Report the value on every change](#report-the-value-on-every-change)
- [Report only the delta of changes](#report-only-the-delta-of-changes)
- [Send the results to an analytics endpoint](#send-the-results-to-an-analytics-endpoint)
- [Send the results to Google Analytics](#send-the-results-to-google-analytics)
- [Send the results to Google Tag Manager](#send-the-results-to-google-tag-manager)
- [Load `web-vitals` from a CDN](#load-web-vitals-from-a-cdn)
- [API](#api)
- [Types](#types)
- [Functions](#functions)
- [Development](#development)
- [Browser Support](#browser-support)
## Overview
The `web-vitals` library is a tiny (~1K), modular library for measuring all the [Web Vitals](https://web.dev/vitals/) metrics on real users, in a way that accurately matches how they're measured by Chrome and reported to other Google tools (e.g. [Chrome User Experience Report](https://developers.google.com/web/tools/chrome-user-experience-report), [Page Speed Insights](https://developers.google.com/speed/pagespeed/insights/), [Search Console's Speed Report](https://webmasters.googleblog.com/2019/11/search-console-speed-report.html)).
The library supports all of the [Core Web Vitals](https://web.dev/vitals/#core-web-vitals) as well as all of the [other Web Vitals](https://web.dev/vitals/#other-web-vitals) that can be measured [in the field](https://web.dev/user-centric-performance-metrics/#how-metrics-are-measured):
### Core Web Vitals
- [Cumulative Layout Shift (CLS)](https://web.dev/cls/)
- [First Input Delay (FID)](https://web.dev/fid/)
- [Largest Contentful Paint (LCP)](https://web.dev/lcp/)
### Other Web Vitals
- [First Contentful Paint (FCP)](https://web.dev/fcp/)
- [Time to First Byte (TTFB)](https://web.dev/time-to-first-byte/)
## Installation
You can install this library from npm by running:
```sh
npm install web-vitals
```
_**Note:** If you're not using npm, you can still load `web-vitals` via `<script>` tags from a CDN like [unpkg.com](https://unpkg.com). See the [load `web-vitals` from a CDN](#load-web-vitals-from-a-cdn) usage example below for details._
## Usage
Each of the Web Vitals metrics are exposed as a single function that takes an `onReport` callback. This callback will fire any time either:
- The final value of the metric has been determined.
- The current metric value needs to be [reported right away](https://developers.google.com/web/updates/2018/07/page-lifecycle-api#advice-hidden) (due to the page being unloaded or backgrounded).
### Log the results to the console
The following example logs the result of each metric to the console once its value is ready to report.
```js
import {getCLS, getFID, getLCP} from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
```
_**Note:** some of these metrics will not report until the user has interacted with the page, switched tabs, or the page starts to unload. If you don't see the values logged to the console immediately, try switching tabs and then switching back._
### Report the value on every change
In most cases, you only want to call `onReport` when the metric is ready. However, for metrics like LCP and CLS (where the value may change over time) you can pass an optional, second argument (`reportAllChanges`). If `true` then `onReport` will be called any time the value of the metric changes, or once the final value has been determined.
This could be useful if, for example, you want to report the current LCP candidate as the page is loading, or you want to report layout shifts (and the current CLS value) as users are interacting with the page. In general, though, using `reportAllChanges` is not needed (or recommended).
```js
import {getCLS, getFID, getLCP} from 'web-vitals';
getCLS(console.log, true);
getFID(console.log); // Does not take a `reportAllChanges` param.
getLCP(console.log, true);
```
_**Note:** when using the `reportAllChanges` option, pay attention to the `isFinal` property of the reported metric, which will indicate whether or not the value might change in the future. See the [API](#api) reference below for more details._
### Report only the delta of changes
Some analytics providers allow you to update the value of a metric, even after you've already sent it to their servers (overwriting the previously-sent value with the same `id`).
Other analytics providers, however, do not allow this, so instead of reporting the new value, you need to report only the delta (the difference between the current value and the last-reported value). You can then compute the total value by summing all metric deltas sent with the same ID.
The following example shows how to use the `id` and `delta` properties:
```js
import {getCLS, getFID, getLCP} from 'web-vitals';
function logDelta({name, id, delta}) {
console.log(`${name} matching ID ${id} changed by ${delta}`);
}
getCLS(logDelta);
getFID(logDelta);
getLCP(logDelta);
```
_**Note:** the first time the `onReport` function is called, its `value` and `delta` properties will be the same._
### Send the results to an analytics endpoint
The following example measures each of the Core Web Vitals metrics and reports them to a hypothetical `/analytics` endpoint, as soon as each is ready to be sent.
The `sendToAnalytics()` function uses the [`navigator.sendBeacon()`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon) method (if available), but falls back to the [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API when not.
```js
import {getCLS, getFID, getLCP} from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify(metric);
// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
(navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
fetch('/analytics', {body, method: 'POST', keepalive: true});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
```
### Send the results to Google Analytics
Google Analytics does not support reporting metric distributions in any of its built-in reports; however, if you set a unique dimension value (in this case, the metric `id`) on every metric instance that you send to Google Analytics, including that dimension in a custom report will allow you to construct a distribution manually.
Using the [Google Analytics Reporting API](https://developers.google.com/analytics/devguides/reporting) and a tool like [Data Studio](https://datastudio.google.com/) (or your own visualization library), you can create dashboards with histograms reporting quantile data (the 75th percentile is recommended) for all of the Web Vitals metrics.
The following code examples show how to send your metrics to Google Analytics in order to enable reporting quantile data:
#### Using `analytics.js`
```js
import {getCLS, getFID, getLCP} from 'web-vitals';
function sendToGoogleAnalytics({name, delta, id}) {
// Assumes the global `ga()` function exists, see:
// https://developers.google.com/analytics/devguides/collection/analyticsjs
ga('send', 'event', {
eventCategory: 'Web Vitals',
eventAction: name,
// Google Analytics metrics must be integers, so the value is rounded.
// For CLS the value is first multiplied by 1000 for greater precision
// (note: increase the multiplier for greater precision if needed).
eventValue: Math.round(name === 'CLS' ? delta * 1000 : delta),
// The `id` value will be unique to the current page load. When sending
// multiple values from the same page (e.g. for CLS), Google Analytics can
// compute a total by grouping on this ID (note: requires `eventLabel` to
// be a dimension in your report).
eventLabel: id,
// Use a non-interaction event to avoid affecting bounce rate.
nonInteraction: true,
});
}
getCLS(sendToGoogleAnalytics);
getFID(sendToGoogleAnalytics);
getLCP(sendToGoogleAnalytics);
```
#### Using `gtag.js`
```js
import {getCLS, getFID, getLCP} from 'web-vitals';
function sendToGoogleAnalytics({name, delta, id}) {
// Assumes the global `gtag()` function exists, see:
// https://developers.google.com/analytics/devguides/collection/gtagjs
gtag('event', name, {
event_category: 'Web Vitals',
// Google Analytics metrics must be integers, so the value is rounded.
// For CLS the value is first multiplied by 1000 for greater precision
// (note: increase the multiplier for greater precision if needed).
value: Math.round(name === 'CLS' ? delta * 1000 : delta),
// The `id` value will be unique to the current page load. When sending
// multiple values from the same page (e.g. for CLS), Google Analytics can
// compute a total by grouping on this ID (note: requires `eventLabel` to
// be a dimension in your report).
event_label: id,
// Use a non-interaction event to avoid affecting bounce rate.
non_interaction: true,
});
}
getCLS(sendToGoogleAnalytics);
getFID(sendToGoogleAnalytics);
getLCP(sendToGoogleAnalytics);
```
### Send the results to Google Tag Manager
The following example measures each of the Core Web Vitals metrics and sends them as separate `dataLayer-events` to be used by Google Tag Manager. With the `web-vitals` trigger you send the metrics to any tag inside your account (see [this comment](https://github.com/GoogleChrome/web-vitals/pull/28#discussion_r422701126) for implementation details).
```js
import {getCLS, getFID, getLCP} from 'web-vitals';
function sendToGTM({name, delta, id}) {
// Assumes the global `dataLayer` array exists, see:
// https://developers.google.com/tag-manager/devguide
dataLayer.push({
event: 'web-vitals',
event_category: 'Web Vitals',
event_action: name,
// Google Analytics metrics must be integers, so the value is rounded.
// For CLS the value is first multiplied by 1000 for greater precision
// (note: increase the multiplier for greater precision if needed).
event_value: Math.round(name === 'CLS' ? delta * 1000 : delta),
// The `id` value will be unique to the current page load. When sending
// multiple values from the same page (e.g. for CLS), Google Analytics can
// compute a total by grouping on this ID (note: requires `eventLabel` to
// be a dimension in your report).
event_label: id,
});
}
getCLS(sendToGTM);
getFID(sendToGTM);
getLCP(sendToGTM);
```
### Load `web-vitals` from a CDN
The recommended way to use the `web-vitals` package is to install it from npm and integrate it into your build process. However, if you're not using npm, it's still possible to use `web-vitals` by requesting it from a CDN that serves npm package files.
The following examples show how to load `web-vitals` from [unpkg.com](https://unpkg.com) using either classic or module scripts:
```html
<!-- Load `web-vitals` using a classic script that sets the global `webVitals` object. -->
<script defer src="https://unpkg.com/web-vitals@0.2.4/dist/web-vitals.es5.umd.min.js"></script>
<script>
addEventListener('DOMContentLoaded', function() {
webVitals.getCLS(console.log);
webVitals.getFID(console.log);
webVitals.getLCP(console.log);
});
</script>
```
```html
<!-- Load `web-vitals` using a module script. -->
<script type="module">
import {getCLS, getFID, getLCP} from 'https://unpkg.com/web-vitals@0.2.4/dist/web-vitals.es5.min.js?module';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
</script>
```
_**Note:** it's safe to use module scripts in legacy browsers because unknown script types are ignored._
## API
### Types:
#### `Metric`
```ts
interface Metric {
// The name of the metric (in acronym form).
name: 'CLS' | 'FCP' | 'FID' | 'LCP' | 'TTFB';
// The current value of the metric.
value: number;
// The delta between the current value and the last-reported value.
// On the first report, `delta` and `value` will always be the same.
delta: number;
// A unique ID representing this particular metric that's specific to the
// current page. This ID can be used by an analytics tool to dedupe
// multiple values sent for the same metric, or to group multiple deltas
// together and calculate a total.
id: string;
// `false` if the value of the metric may change in the future,
// for the current page.
isFinal: boolean;
// Any performance entries used in the metric value calculation.
// Note, entries will be added to the array as the value changes.
entries: PerformanceEntry[];
}
```
#### `ReportHandler`
```ts
interface ReportHandler {
(metric: Metric): void;
}
```
### Functions:
#### `getCLS()`
```ts
type getCLS = (onReport: ReportHandler, reportAllChanges?: boolean) => void
```
Calculates the [CLS](https://web.dev/cls/) value for the current page and calls the `onReport` function once the value is ready to be reported, along with all `layout-shift` performance entries that were used in the metric value calculation. The reported value is a [double](https://heycam.github.io/webidl/#idl-double) (corresponding to a [layout shift value](https://wicg.github.io/layout-instability/#layout-shift-value)).
If the `reportAllChanges` param is `true`, the `onReport` function will be called any time a new `layout-shift` performance entry is dispatched, or once the final value of the metric has been determined.
_**Important:** unlike other metrics, CLS continues to monitor changes for the entire lifespan of the page&mdash;including if the user returns to the page after it's been hidden/backgrounded. However, since browsers often [will not fire additional callbacks once the user has backgrounded a page](https://developers.google.com/web/updates/2018/07/page-lifecycle-api#advice-hidden), `onReport` is always called when the page's visibility state changes to hidden. As a result, the `onReport` function might be called multiple times during the same page load (see [Reporting only the delta of changes](#report-only-the-delta-of-changes) for how to manage this)._
#### `getFCP()`
```ts
type getFCP = (onReport: ReportHandler) => void
```
Calculates the [FCP](https://web.dev/fcp/) value for the current page and calls the `onReport` function once the value is ready, along with the relevant `paint` performance entry used to determine the value. The reported value is a `DOMHighResTimeStamp`.
#### `getFID()`
```ts
type getFID = (onReport: ReportHandler) => void
```
Calculates the [FID](https://web.dev/fid/) value for the current page and calls the `onReport` function once the value is ready, along with the relevant `first-input` performance entry used to determine the value (and optionally the input event if using the [FID polyfill](#fid-polyfill)). The reported value is a `DOMHighResTimeStamp`.
_**Important:** since FID is only reported after the user interacts with the page, it's possible that it will not be reported for some page loads._
#### `getLCP()`
```ts
type getLCP = (onReport: ReportHandler, reportAllChanges?: boolean) => void
```
Calculates the [LCP](https://web.dev/lcp/) value for the current page and calls the `onReport` function once the value is ready (along with the relevant `largest-contentful-paint` performance entries used to determine the value). The reported value is a `DOMHighResTimeStamp`.
If the `reportAllChanges` param is `true`, the `onReport` function will be called any time a new `largest-contentful-paint` performance entry is dispatched, or once the final value of the metric has been determined.
#### `getTTFB()`
```ts
type getTTFB = (onReport: ReportHandler) => void
```
Calculates the [TTFB](https://web.dev/time-to-first-byte/) value for the current page and calls the `onReport` function once the page has loaded, along with the relevant `navigation` performance entry used to determine the value. The reported value is a `DOMHighResTimeStamp`.
Note, this function waits until after the page is loaded to call `onReport` in order to ensure all properties of the `navigation` entry are populated. This is useful if you want to report on other metrics exposed by the [Navigation Timing API](https://w3c.github.io/navigation-timing/).
For example, the TTFB metric starts from the page's [time origin](https://www.w3.org/TR/hr-time-2/#sec-time-origin), which means it [includes](https://developers.google.com/web/fundamentals/performance/navigation-and-resource-timing#the_life_and_timings_of_a_network_request) time spent on DNS lookup, connection negotiation, network latency, and unloading the previous document. If, in addition to TTFB, you want a metric that excludes these timings and _just_ captures the time spent making the request and receiving the first byte of the response, you could compute that from data found on the performance entry:
```js
import {getTTFB} from 'web-vitals';
getTTFB((metric) => {
// Calculate the request time by subtracting from TTFB
// everything that happened prior to the request starting.
const requestTime = metric.value - metric.entries[0].requestStart;
console.log('Request time:', requestTime);
});
```
_**Note:** browsers that do not support `navigation` entries will fall back to
using `performance.timing` (with the timestamps converted from epoch time to `DOMHighResTimeStamp`). This ensures code referencing these values (like in the example above) will work the same in all browsers._
## Browser Support
This code has been tested and will run without error in all major browsers as well as Internet Explorer back to version 9 (when transpiled to ES5). However, some of the APIs required to capture these metrics are only available in Chromium-based browsers (e.g. Chrome, Edge, Opera, Samsung Internet).
Browser support for each function is as follows:
- `getCLS()`: Chromium
- `getFCP()`: Chromium
- `getFID()`: Chromium, Firefox, Safari, Internet Explorer (with polyfill, [see below](#fid-polyfill))
- `getLCP()`: Chromium
- `getTTFB()`: Chromium, Firefox, Safari, Internet Explorer
### FID Polyfill
The `getFID()` function will work in all browsers if the page has included the [FID polyfill](https://github.com/GoogleChromeLabs/first-input-delay).
Browsers that support the native [Event Timing API](https://wicg.github.io/event-timing/) will use that and report the metric value from the `first-input` performance entry.
Browsers that **do not** support the native Event Timing API will use the value reported by the polyfill, and the `entries` array will contain a plain-object version of the native [`PerformanceEventTiming`](https://wicg.github.io/event-timing/#sec-performance-event-timing) object.
_**Note:** the `duration` and `processingEnd` properties of the `PerformanceEventTiming` will not be present, as they're not exposed by the polyfill._
## Development
### Building the code
The `web-vitals` source code is written in TypeScript. To transpile the code and build the production bundles, run the following command.
```sh
npm run build
```
To build the code and watch for changes, run:
```sh
npm run watch
```
### Running the tests
The `web-vitals` code is tested in real browsers using [webdriver.io](https://webdriver.io/). Use the following command to run the tests:
```sh
npm test
```
To test any of the APIs manually, you can start the test server
```sh
npm run test:server
```
Then navigate to `http://localhost:9090/test/<view>`, where `<view>` is the basename of one the templates under [/test/views/](/test/views/).
You'll likely want to combine this with `npm run watch` to ensure any changes you make are transpiled and rebuilt.
## License
[Apache 2.0](/LICENSE)

2
node_modules/web-vitals/dist/getCLS.d.ts generated vendored Normal file
View file

@ -0,0 +1,2 @@
import { ReportHandler } from './types.js';
export declare const getCLS: (onReport: ReportHandler, reportAllChanges?: boolean) => void;

42
node_modules/web-vitals/dist/getCLS.js generated vendored Normal file
View file

@ -0,0 +1,42 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { initMetric } from './lib/initMetric.js';
import { observe } from './lib/observe.js';
import { onHidden } from './lib/onHidden.js';
import { bindReporter } from './lib/bindReporter.js';
export const getCLS = (onReport, reportAllChanges = false) => {
const metric = initMetric('CLS', 0);
let report;
const entryHandler = (entry) => {
// Only count layout shifts without recent user input.
if (!entry.hadRecentInput) {
metric.value += entry.value;
metric.entries.push(entry);
report();
}
};
const po = observe('layout-shift', entryHandler);
if (po) {
report = bindReporter(onReport, metric, po, reportAllChanges);
onHidden(({ isUnloading }) => {
po.takeRecords().map(entryHandler);
if (isUnloading) {
metric.isFinal = true;
}
report();
});
}
};

2
node_modules/web-vitals/dist/getFCP.d.ts generated vendored Normal file
View file

@ -0,0 +1,2 @@
import { ReportHandler } from './types.js';
export declare const getFCP: (onReport: ReportHandler) => void;

39
node_modules/web-vitals/dist/getFCP.js generated vendored Normal file
View file

@ -0,0 +1,39 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { bindReporter } from './lib/bindReporter.js';
import { getFirstHidden } from './lib/getFirstHidden.js';
import { initMetric } from './lib/initMetric.js';
import { observe } from './lib/observe.js';
export const getFCP = (onReport) => {
const metric = initMetric('FCP');
const firstHidden = getFirstHidden();
let report;
const entryHandler = (entry) => {
if (entry.name === 'first-contentful-paint') {
// Only report if the page wasn't hidden prior to the first paint.
if (entry.startTime < firstHidden.timeStamp) {
metric.value = entry.startTime;
metric.isFinal = true;
metric.entries.push(entry);
report();
}
}
};
const po = observe('paint', entryHandler);
if (po) {
report = bindReporter(onReport, metric, po);
}
};

14
node_modules/web-vitals/dist/getFID.d.ts generated vendored Normal file
View file

@ -0,0 +1,14 @@
import { ReportHandler } from './types.js';
interface FIDPolyfillCallback {
(value: number, event: Event): void;
}
interface FIDPolyfill {
onFirstInputDelay: (onReport: FIDPolyfillCallback) => void;
}
declare global {
interface Window {
perfMetrics: FIDPolyfill;
}
}
export declare const getFID: (onReport: ReportHandler) => void;
export {};

61
node_modules/web-vitals/dist/getFID.js generated vendored Normal file
View file

@ -0,0 +1,61 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { bindReporter } from './lib/bindReporter.js';
import { getFirstHidden } from './lib/getFirstHidden.js';
import { initMetric } from './lib/initMetric.js';
import { observe } from './lib/observe.js';
import { onHidden } from './lib/onHidden.js';
export const getFID = (onReport) => {
const metric = initMetric('FID');
const firstHidden = getFirstHidden();
const entryHandler = (entry) => {
// Only report if the page wasn't hidden prior to the first input.
if (entry.startTime < firstHidden.timeStamp) {
metric.value = entry.processingStart - entry.startTime;
metric.entries.push(entry);
metric.isFinal = true;
report();
}
};
const po = observe('first-input', entryHandler);
const report = bindReporter(onReport, metric, po);
if (po) {
onHidden(() => {
po.takeRecords().map(entryHandler);
po.disconnect();
}, true);
}
else {
if (window.perfMetrics && window.perfMetrics.onFirstInputDelay) {
window.perfMetrics.onFirstInputDelay((value, event) => {
// Only report if the page wasn't hidden prior to the first input.
if (event.timeStamp < firstHidden.timeStamp) {
metric.value = value;
metric.isFinal = true;
metric.entries = [{
entryType: 'first-input',
name: event.type,
target: event.target,
cancelable: event.cancelable,
startTime: event.timeStamp,
processingStart: event.timeStamp + value,
}];
report();
}
});
}
}
};

2
node_modules/web-vitals/dist/getLCP.d.ts generated vendored Normal file
View file

@ -0,0 +1,2 @@
import { ReportHandler } from './types.js';
export declare const getLCP: (onReport: ReportHandler, reportAllChanges?: boolean) => void;

54
node_modules/web-vitals/dist/getLCP.js generated vendored Normal file
View file

@ -0,0 +1,54 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { bindReporter } from './lib/bindReporter.js';
import { getFirstHidden } from './lib/getFirstHidden.js';
import { initMetric } from './lib/initMetric.js';
import { observe } from './lib/observe.js';
import { onHidden } from './lib/onHidden.js';
import { whenInput } from './lib/whenInput.js';
export const getLCP = (onReport, reportAllChanges = false) => {
const metric = initMetric('LCP');
const firstHidden = getFirstHidden();
let report;
const entryHandler = (entry) => {
// The startTime attribute returns the value of the renderTime if it is not 0,
// and the value of the loadTime otherwise.
const value = entry.startTime;
// If the page was hidden prior to paint time of the entry,
// ignore it and mark the metric as final, otherwise add the entry.
if (value < firstHidden.timeStamp) {
metric.value = value;
metric.entries.push(entry);
}
else {
metric.isFinal = true;
}
report();
};
const po = observe('largest-contentful-paint', entryHandler);
if (po) {
report = bindReporter(onReport, metric, po, reportAllChanges);
const onFinal = () => {
if (!metric.isFinal) {
po.takeRecords().map(entryHandler);
metric.isFinal = true;
report();
}
};
whenInput().then(onFinal);
onHidden(onFinal, true);
}
};

2
node_modules/web-vitals/dist/getTTFB.d.ts generated vendored Normal file
View file

@ -0,0 +1,2 @@
import { ReportHandler } from './types.js';
export declare const getTTFB: (onReport: ReportHandler) => void;

58
node_modules/web-vitals/dist/getTTFB.js generated vendored Normal file
View file

@ -0,0 +1,58 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { initMetric } from './lib/initMetric.js';
const afterLoad = (callback) => {
if (document.readyState === 'complete') {
// Queue a task so the callback runs after `loadEventEnd`.
setTimeout(callback, 0);
}
else {
// Use `pageshow` so the callback runs after `loadEventEnd`.
addEventListener('pageshow', callback);
}
};
const getNavigationEntryFromPerformanceTiming = () => {
// Really annoying that TypeScript errors when using `PerformanceTiming`.
const timing = performance.timing;
const navigationEntry = {
entryType: 'navigation',
startTime: 0,
};
for (const key in timing) {
if (key !== 'navigationStart' && key !== 'toJSON') {
navigationEntry[key] = Math.max(timing[key] - timing.navigationStart, 0);
}
}
return navigationEntry;
};
export const getTTFB = (onReport) => {
const metric = initMetric('TTFB');
afterLoad(() => {
try {
// Use the NavigationTiming L2 entry if available.
const navigationEntry = performance.getEntriesByType('navigation')[0] ||
getNavigationEntryFromPerformanceTiming();
metric.value = metric.delta =
navigationEntry.responseStart;
metric.entries = [navigationEntry];
metric.isFinal = true;
onReport(metric);
}
catch (error) {
// Do nothing.
}
});
};

6
node_modules/web-vitals/dist/index.d.ts generated vendored Normal file
View file

@ -0,0 +1,6 @@
export { getCLS } from './getCLS.js';
export { getFCP } from './getFCP.js';
export { getFID } from './getFID.js';
export { getLCP } from './getLCP.js';
export { getTTFB } from './getTTFB.js';
export * from './types.js';

21
node_modules/web-vitals/dist/index.js generated vendored Normal file
View file

@ -0,0 +1,21 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { getCLS } from './getCLS.js';
export { getFCP } from './getFCP.js';
export { getFID } from './getFID.js';
export { getLCP } from './getLCP.js';
export { getTTFB } from './getTTFB.js';
export * from './types.js';

2
node_modules/web-vitals/dist/lib/bindReporter.d.ts generated vendored Normal file
View file

@ -0,0 +1,2 @@
import { Metric, ReportHandler } from '../types.js';
export declare const bindReporter: (callback: ReportHandler, metric: Metric, po: PerformanceObserver | undefined, observeAllUpdates?: boolean | undefined) => () => void;

38
node_modules/web-vitals/dist/lib/bindReporter.js generated vendored Normal file
View file

@ -0,0 +1,38 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const bindReporter = (callback, metric, po, observeAllUpdates) => {
let prevValue;
return () => {
if (po && metric.isFinal) {
po.disconnect();
}
if (metric.value >= 0) {
if (observeAllUpdates ||
metric.isFinal ||
document.visibilityState === 'hidden') {
metric.delta = metric.value - (prevValue || 0);
// Report the metric if there's a non-zero delta, if the metric is
// final, or if no previous value exists (which can happen in the case
// of the document becoming hidden when the metric value is 0).
// See: https://github.com/GoogleChrome/web-vitals/issues/14
if (metric.delta || metric.isFinal || prevValue === undefined) {
callback(metric);
prevValue = metric.value;
}
}
}
};
};

View file

@ -0,0 +1,6 @@
/**
* Performantly generate a unique, 27-char string by combining the current
* timestamp with a 13-digit random number.
* @return {string}
*/
export declare const generateUniqueID: () => string;

23
node_modules/web-vitals/dist/lib/generateUniqueID.js generated vendored Normal file
View file

@ -0,0 +1,23 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Performantly generate a unique, 27-char string by combining the current
* timestamp with a 13-digit random number.
* @return {string}
*/
export const generateUniqueID = () => {
return `${Date.now()}-${Math.floor(Math.random() * (9e12 - 1)) + 1e12}`;
};

3
node_modules/web-vitals/dist/lib/getFirstHidden.d.ts generated vendored Normal file
View file

@ -0,0 +1,3 @@
export declare const getFirstHidden: () => {
readonly timeStamp: number;
};

33
node_modules/web-vitals/dist/lib/getFirstHidden.js generated vendored Normal file
View file

@ -0,0 +1,33 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { onHidden } from './onHidden.js';
let firstHiddenTime;
export const getFirstHidden = () => {
if (firstHiddenTime === undefined) {
// If the document is hidden when this code runs, assume it was hidden
// since navigation start. This isn't a perfect heuristic, but it's the
// best we can do until an API is available to support querying past
// visibilityState.
firstHiddenTime = document.visibilityState === 'hidden' ? 0 : Infinity;
// Update the time if/when the document becomes hidden.
onHidden(({ timeStamp }) => firstHiddenTime = timeStamp, true);
}
return {
get timeStamp() {
return firstHiddenTime;
}
};
};

2
node_modules/web-vitals/dist/lib/initMetric.d.ts generated vendored Normal file
View file

@ -0,0 +1,2 @@
import { Metric } from '../types.js';
export declare const initMetric: (name: Metric['name'], value?: number) => Metric;

26
node_modules/web-vitals/dist/lib/initMetric.js generated vendored Normal file
View file

@ -0,0 +1,26 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { generateUniqueID } from './generateUniqueID.js';
export const initMetric = (name, value = -1) => {
return {
name,
value,
delta: 0,
entries: [],
id: generateUniqueID(),
isFinal: false
};
};

12
node_modules/web-vitals/dist/lib/observe.d.ts generated vendored Normal file
View file

@ -0,0 +1,12 @@
export interface PerformanceEntryHandler {
(entry: PerformanceEntry): void;
}
/**
* Takes a performance entry type and a callback function, and creates a
* `PerformanceObserver` instance that will observe the specified entry type
* with buffering enabled and call the callback _for each entry_.
*
* This function also feature-detects entry support and wraps the logic in a
* try/catch to avoid errors in unsupporting browsers.
*/
export declare const observe: (type: string, callback: PerformanceEntryHandler) => PerformanceObserver | undefined;

36
node_modules/web-vitals/dist/lib/observe.js generated vendored Normal file
View file

@ -0,0 +1,36 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Takes a performance entry type and a callback function, and creates a
* `PerformanceObserver` instance that will observe the specified entry type
* with buffering enabled and call the callback _for each entry_.
*
* This function also feature-detects entry support and wraps the logic in a
* try/catch to avoid errors in unsupporting browsers.
*/
export const observe = (type, callback) => {
try {
if (PerformanceObserver.supportedEntryTypes.includes(type)) {
const po = new PerformanceObserver((l) => l.getEntries().map(callback));
po.observe({ type, buffered: true });
return po;
}
}
catch (e) {
// Do nothing.
}
return;
};

7
node_modules/web-vitals/dist/lib/onHidden.d.ts generated vendored Normal file
View file

@ -0,0 +1,7 @@
export interface OnHiddenCallback {
({ timeStamp, isUnloading }: {
timeStamp: number;
isUnloading: boolean;
}): void;
}
export declare const onHidden: (cb: OnHiddenCallback, once?: boolean) => void;

38
node_modules/web-vitals/dist/lib/onHidden.js generated vendored Normal file
View file

@ -0,0 +1,38 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let isUnloading = false;
let listenersAdded = false;
const onPageHide = (event) => {
isUnloading = !event.persisted;
};
const addListeners = () => {
addEventListener('pagehide', onPageHide);
// `beforeunload` is needed to fix this bug:
// https://bugs.chromium.org/p/chromium/issues/detail?id=987409
// eslint-disable-next-line @typescript-eslint/no-empty-function
addEventListener('beforeunload', () => { });
};
export const onHidden = (cb, once = false) => {
if (!listenersAdded) {
addListeners();
listenersAdded = true;
}
addEventListener('visibilitychange', ({ timeStamp }) => {
if (document.visibilityState === 'hidden') {
cb({ timeStamp, isUnloading });
}
}, { capture: true, once });
};

1
node_modules/web-vitals/dist/lib/whenInput.d.ts generated vendored Normal file
View file

@ -0,0 +1 @@
export declare const whenInput: () => Promise<Event>;

30
node_modules/web-vitals/dist/lib/whenInput.js generated vendored Normal file
View file

@ -0,0 +1,30 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let inputPromise;
export const whenInput = () => {
if (!inputPromise) {
inputPromise = new Promise((r) => {
return ['scroll', 'keydown', 'pointerdown'].map((type) => {
addEventListener(type, r, {
once: true,
passive: true,
capture: true,
});
});
});
}
return inputPromise;
};

11
node_modules/web-vitals/dist/types.d.ts generated vendored Normal file
View file

@ -0,0 +1,11 @@
export interface Metric {
name: 'CLS' | 'FCP' | 'FID' | 'LCP' | 'TTFB';
value: number;
delta: number;
id: string;
isFinal: boolean;
entries: PerformanceEntry[];
}
export interface ReportHandler {
(metric: Metric): void;
}

15
node_modules/web-vitals/dist/types.js generated vendored Normal file
View file

@ -0,0 +1,15 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

1
node_modules/web-vitals/dist/web-vitals.es5.min.js generated vendored Normal file
View file

@ -0,0 +1 @@
var t,n,e=function(){return"".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)},i=function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;return{name:t,value:n,delta:0,entries:[],id:e(),isFinal:!1}},a=function(t,n){try{if(PerformanceObserver.supportedEntryTypes.includes(t)){var e=new PerformanceObserver((function(t){return t.getEntries().map(n)}));return e.observe({type:t,buffered:!0}),e}}catch(t){}},r=!1,o=!1,s=function(t){r=!t.persisted},u=function(){addEventListener("pagehide",s),addEventListener("beforeunload",(function(){}))},c=function(t){var n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];o||(u(),o=!0),addEventListener("visibilitychange",(function(n){var e=n.timeStamp;"hidden"===document.visibilityState&&t({timeStamp:e,isUnloading:r})}),{capture:!0,once:n})},l=function(t,n,e,i){var a;return function(){e&&n.isFinal&&e.disconnect(),n.value>=0&&(i||n.isFinal||"hidden"===document.visibilityState)&&(n.delta=n.value-(a||0),(n.delta||n.isFinal||void 0===a)&&(t(n),a=n.value))}},p=function(t){var n,e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=i("CLS",0),o=function(t){t.hadRecentInput||(r.value+=t.value,r.entries.push(t),n())},s=a("layout-shift",o);s&&(n=l(t,r,s,e),c((function(t){var e=t.isUnloading;s.takeRecords().map(o),e&&(r.isFinal=!0),n()})))},d=function(){return void 0===t&&(t="hidden"===document.visibilityState?0:1/0,c((function(n){var e=n.timeStamp;return t=e}),!0)),{get timeStamp(){return t}}},v=function(t){var n,e=i("FCP"),r=d(),o=a("paint",(function(t){"first-contentful-paint"===t.name&&t.startTime<r.timeStamp&&(e.value=t.startTime,e.isFinal=!0,e.entries.push(t),n())}));o&&(n=l(t,e,o))},f=function(t){var n=i("FID"),e=d(),r=function(t){t.startTime<e.timeStamp&&(n.value=t.processingStart-t.startTime,n.entries.push(t),n.isFinal=!0,s())},o=a("first-input",r),s=l(t,n,o);o?c((function(){o.takeRecords().map(r),o.disconnect()}),!0):window.perfMetrics&&window.perfMetrics.onFirstInputDelay&&window.perfMetrics.onFirstInputDelay((function(t,i){i.timeStamp<e.timeStamp&&(n.value=t,n.isFinal=!0,n.entries=[{entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+t}],s())}))},m=function(){return n||(n=new Promise((function(t){return["scroll","keydown","pointerdown"].map((function(n){addEventListener(n,t,{once:!0,passive:!0,capture:!0})}))}))),n},g=function(t){var n,e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=i("LCP"),o=d(),s=function(t){var e=t.startTime;e<o.timeStamp?(r.value=e,r.entries.push(t)):r.isFinal=!0,n()},u=a("largest-contentful-paint",s);if(u){n=l(t,r,u,e);var p=function(){r.isFinal||(u.takeRecords().map(s),r.isFinal=!0,n())};m().then(p),c(p,!0)}},h=function(t){var n,e=i("TTFB");n=function(){try{var n=performance.getEntriesByType("navigation")[0]||function(){var t=performance.timing,n={entryType:"navigation",startTime:0};for(var e in t)"navigationStart"!==e&&"toJSON"!==e&&(n[e]=Math.max(t[e]-t.navigationStart,0));return n}();e.value=e.delta=n.responseStart,e.entries=[n],e.isFinal=!0,t(e)}catch(t){}},"complete"===document.readyState?setTimeout(n,0):addEventListener("pageshow",n)};export{p as getCLS,v as getFCP,f as getFID,g as getLCP,h as getTTFB};

View file

@ -0,0 +1 @@
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).webVitals={})}(this,(function(t){"use strict";var e,n,i=function(){return"".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)},a=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;return{name:t,value:e,delta:0,entries:[],id:i(),isFinal:!1}},r=function(t,e){try{if(PerformanceObserver.supportedEntryTypes.includes(t)){var n=new PerformanceObserver((function(t){return t.getEntries().map(e)}));return n.observe({type:t,buffered:!0}),n}}catch(t){}},o=!1,s=!1,u=function(t){o=!t.persisted},c=function(){addEventListener("pagehide",u),addEventListener("beforeunload",(function(){}))},f=function(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1];s||(c(),s=!0),addEventListener("visibilitychange",(function(e){var n=e.timeStamp;"hidden"===document.visibilityState&&t({timeStamp:n,isUnloading:o})}),{capture:!0,once:e})},d=function(t,e,n,i){var a;return function(){n&&e.isFinal&&n.disconnect(),e.value>=0&&(i||e.isFinal||"hidden"===document.visibilityState)&&(e.delta=e.value-(a||0),(e.delta||e.isFinal||void 0===a)&&(t(e),a=e.value))}},l=function(){return void 0===e&&(e="hidden"===document.visibilityState?0:1/0,f((function(t){var n=t.timeStamp;return e=n}),!0)),{get timeStamp(){return e}}},p=function(){return n||(n=new Promise((function(t){return["scroll","keydown","pointerdown"].map((function(e){addEventListener(e,t,{once:!0,passive:!0,capture:!0})}))}))),n};t.getCLS=function(t){var e,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=a("CLS",0),o=function(t){t.hadRecentInput||(i.value+=t.value,i.entries.push(t),e())},s=r("layout-shift",o);s&&(e=d(t,i,s,n),f((function(t){var n=t.isUnloading;s.takeRecords().map(o),n&&(i.isFinal=!0),e()})))},t.getFCP=function(t){var e,n=a("FCP"),i=l(),o=r("paint",(function(t){"first-contentful-paint"===t.name&&t.startTime<i.timeStamp&&(n.value=t.startTime,n.isFinal=!0,n.entries.push(t),e())}));o&&(e=d(t,n,o))},t.getFID=function(t){var e=a("FID"),n=l(),i=function(t){t.startTime<n.timeStamp&&(e.value=t.processingStart-t.startTime,e.entries.push(t),e.isFinal=!0,s())},o=r("first-input",i),s=d(t,e,o);o?f((function(){o.takeRecords().map(i),o.disconnect()}),!0):window.perfMetrics&&window.perfMetrics.onFirstInputDelay&&window.perfMetrics.onFirstInputDelay((function(t,i){i.timeStamp<n.timeStamp&&(e.value=t,e.isFinal=!0,e.entries=[{entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+t}],s())}))},t.getLCP=function(t){var e,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=a("LCP"),o=l(),s=function(t){var n=t.startTime;n<o.timeStamp?(i.value=n,i.entries.push(t)):i.isFinal=!0,e()},u=r("largest-contentful-paint",s);if(u){e=d(t,i,u,n);var c=function(){i.isFinal||(u.takeRecords().map(s),i.isFinal=!0,e())};p().then(c),f(c,!0)}},t.getTTFB=function(t){var e,n=a("TTFB");e=function(){try{var e=performance.getEntriesByType("navigation")[0]||function(){var t=performance.timing,e={entryType:"navigation",startTime:0};for(var n in t)"navigationStart"!==n&&"toJSON"!==n&&(e[n]=Math.max(t[n]-t.navigationStart,0));return e}();n.value=n.delta=e.responseStart,n.entries=[e],n.isFinal=!0,t(n)}catch(t){}},"complete"===document.readyState?setTimeout(e,0):addEventListener("pageshow",e)},Object.defineProperty(t,"__esModule",{value:!0})}));

86
node_modules/web-vitals/package.json generated vendored Normal file
View file

@ -0,0 +1,86 @@
{
"name": "web-vitals",
"version": "0.2.4",
"description": "Easily measure performance metrics in JavaScript",
"main": "dist/web-vitals.es5.umd.min.js",
"module": "dist/web-vitals.es5.min.js",
"typings": "dist/index.d.ts",
"files": [
"dist",
"src"
],
"scripts": {
"build": "run-s clean build:ts build:js",
"build:ts": "tsc -b",
"build:js": "rollup -c",
"clean": "rm -rf dist tsconfig.tsbuildinfo",
"dev": "run-p watch serve",
"lint": "eslint \"*.js\" \"src/**/*.ts\" \"test/**/*.js\"",
"lint:fix": "eslint --fix \"*.js\" \"src/**/*.ts\" \"test/**/*.js\"",
"postversion": "git push --follow-tags",
"release:major": "npm version major -m 'Release v%s' && npm publish",
"release:minor": "npm version minor -m 'Release v%s' && npm publish",
"release:patch": "npm version patch -m 'Release v%s' && npm publish",
"test": "npm-run-all build -p -r test:*",
"test:e2e": "wdio wdio.conf.js",
"test:server": "node test/server.js",
"watch": "run-p watch:*",
"watch:ts": "tsc -b -w",
"watch:js": "rollup -c -w",
"version": "run-s build"
},
"keywords": [
"crux",
"performance",
"metrics",
"CLS",
"FCP",
"FID",
"LCP",
"TTFB"
],
"author": {
"name": "Philip Walton",
"email": "philip@philipwalton.com",
"url": "http://philipwalton.com"
},
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/GoogleChrome/web-vitals.git"
},
"bugs": {
"url": "https://github.com/GoogleChrome/web-vitals/issues"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
},
"devDependencies": {
"@babel/core": "^7.10.5",
"@babel/preset-env": "^7.10.4",
"@typescript-eslint/eslint-plugin": "^3.7.0",
"@typescript-eslint/parser": "^3.7.0",
"@wdio/cli": "^6.3.4",
"@wdio/local-runner": "^6.3.4",
"@wdio/mocha-framework": "^6.3.0",
"@wdio/selenium-standalone-service": "^6.1.14",
"@wdio/spec-reporter": "^6.3.0",
"babel-eslint": "^10.1.0",
"body-parser": "^1.19.0",
"chromedriver": "^84.0.1",
"eslint": "^7.5.0",
"eslint-config-google": "^0.14.0",
"express": "^4.17.1",
"fs-extra": "^9.0.1",
"husky": "^4.2.5",
"npm-run-all": "^4.1.5",
"nunjucks": "^3.2.2",
"rollup": "^2.23.0",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-terser": "^6.1.0",
"typescript": "^3.9.7",
"wdio-chromedriver-service": "^6.0.3"
}
}

57
node_modules/web-vitals/src/getCLS.ts generated vendored Normal file
View file

@ -0,0 +1,57 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {initMetric} from './lib/initMetric.js';
import {observe, PerformanceEntryHandler} from './lib/observe.js';
import {onHidden} from './lib/onHidden.js';
import {bindReporter} from './lib/bindReporter.js';
import {ReportHandler} from './types.js';
// https://wicg.github.io/layout-instability/#sec-layout-shift
interface LayoutShift extends PerformanceEntry {
value: number;
hadRecentInput: boolean;
}
export const getCLS = (onReport: ReportHandler, reportAllChanges = false) => {
const metric = initMetric('CLS', 0);
let report: ReturnType<typeof bindReporter>;
const entryHandler = (entry: LayoutShift) => {
// Only count layout shifts without recent user input.
if (!entry.hadRecentInput) {
(metric.value as number) += entry.value;
metric.entries.push(entry);
report();
}
};
const po = observe('layout-shift', entryHandler as PerformanceEntryHandler);
if (po) {
report = bindReporter(onReport, metric, po, reportAllChanges);
onHidden(({isUnloading}) => {
po.takeRecords().map(entryHandler as PerformanceEntryHandler);
if (isUnloading) {
metric.isFinal = true;
}
report();
});
}
};

46
node_modules/web-vitals/src/getFCP.ts generated vendored Normal file
View file

@ -0,0 +1,46 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {bindReporter} from './lib/bindReporter.js';
import {getFirstHidden} from './lib/getFirstHidden.js';
import {initMetric} from './lib/initMetric.js';
import {observe} from './lib/observe.js';
import {ReportHandler} from './types.js';
export const getFCP = (onReport: ReportHandler) => {
const metric = initMetric('FCP');
const firstHidden = getFirstHidden();
let report: ReturnType<typeof bindReporter>;
const entryHandler = (entry: PerformanceEntry) => {
if (entry.name === 'first-contentful-paint') {
// Only report if the page wasn't hidden prior to the first paint.
if (entry.startTime < firstHidden.timeStamp) {
metric.value = entry.startTime;
metric.isFinal = true;
metric.entries.push(entry);
report();
}
}
};
const po = observe('paint', entryHandler);
if (po) {
report = bindReporter(onReport, metric, po);
}
};

88
node_modules/web-vitals/src/getFID.ts generated vendored Normal file
View file

@ -0,0 +1,88 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {bindReporter} from './lib/bindReporter.js';
import {getFirstHidden} from './lib/getFirstHidden.js';
import {initMetric} from './lib/initMetric.js';
import {observe, PerformanceEntryHandler} from './lib/observe.js';
import {onHidden} from './lib/onHidden.js';
import {ReportHandler} from './types.js';
interface FIDPolyfillCallback {
(value: number, event: Event): void;
}
interface FIDPolyfill {
onFirstInputDelay: (onReport: FIDPolyfillCallback) => void;
}
declare global {
interface Window {
perfMetrics: FIDPolyfill;
}
}
// https://wicg.github.io/event-timing/#sec-performance-event-timing
interface PerformanceEventTiming extends PerformanceEntry {
processingStart: DOMHighResTimeStamp;
cancelable?: boolean;
target?: Element;
}
export const getFID = (onReport: ReportHandler) => {
const metric = initMetric('FID');
const firstHidden = getFirstHidden();
const entryHandler = (entry: PerformanceEventTiming) => {
// Only report if the page wasn't hidden prior to the first input.
if (entry.startTime < firstHidden.timeStamp) {
metric.value = entry.processingStart - entry.startTime;
metric.entries.push(entry);
metric.isFinal = true;
report();
}
};
const po = observe('first-input', entryHandler as PerformanceEntryHandler);
const report = bindReporter(onReport, metric, po);
if (po) {
onHidden(() => {
po.takeRecords().map(entryHandler as PerformanceEntryHandler);
po.disconnect();
}, true);
} else {
if (window.perfMetrics && window.perfMetrics.onFirstInputDelay) {
window.perfMetrics.onFirstInputDelay((value: number, event: Event) => {
// Only report if the page wasn't hidden prior to the first input.
if (event.timeStamp < firstHidden.timeStamp) {
metric.value = value;
metric.isFinal = true;
metric.entries = [{
entryType: 'first-input',
name: event.type,
target: event.target,
cancelable: event.cancelable,
startTime: event.timeStamp,
processingStart: event.timeStamp + value,
} as PerformanceEventTiming];
report();
}
});
}
}
};

65
node_modules/web-vitals/src/getLCP.ts generated vendored Normal file
View file

@ -0,0 +1,65 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {bindReporter} from './lib/bindReporter.js';
import {getFirstHidden} from './lib/getFirstHidden.js';
import {initMetric} from './lib/initMetric.js';
import {observe, PerformanceEntryHandler} from './lib/observe.js';
import {onHidden} from './lib/onHidden.js';
import {whenInput} from './lib/whenInput.js';
import {ReportHandler} from './types.js';
export const getLCP = (onReport: ReportHandler, reportAllChanges = false) => {
const metric = initMetric('LCP');
const firstHidden = getFirstHidden();
let report: ReturnType<typeof bindReporter>;
const entryHandler = (entry: PerformanceEntry) => {
// The startTime attribute returns the value of the renderTime if it is not 0,
// and the value of the loadTime otherwise.
const value = entry.startTime;
// If the page was hidden prior to paint time of the entry,
// ignore it and mark the metric as final, otherwise add the entry.
if (value < firstHidden.timeStamp) {
metric.value = value;
metric.entries.push(entry);
} else {
metric.isFinal = true;
}
report();
};
const po = observe('largest-contentful-paint', entryHandler);
if (po) {
report = bindReporter(onReport, metric, po, reportAllChanges);
const onFinal = () => {
if (!metric.isFinal) {
po.takeRecords().map(entryHandler as PerformanceEntryHandler);
metric.isFinal = true;
report();
}
}
whenInput().then(onFinal);
onHidden(onFinal, true);
}
};

117
node_modules/web-vitals/src/getTTFB.ts generated vendored Normal file
View file

@ -0,0 +1,117 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {initMetric} from './lib/initMetric.js';
import {ReportHandler} from './types.js';
interface NavigationEntryShim {
// From `PerformanceNavigationTimingEntry`.
entryType: string;
startTime: number;
// From `performance.timing`.
connectEnd?: number;
connectStart?: number;
domComplete?: number;
domContentLoadedEventEnd?: number;
domContentLoadedEventStart?: number;
domInteractive?: number;
domainLookupEnd?: number;
domainLookupStart?: number;
fetchStart?: number;
loadEventEnd?: number;
loadEventStart?: number;
redirectEnd?: number;
redirectStart?: number;
requestStart?: number;
responseEnd?: number;
responseStart?: number;
secureConnectionStart?: number;
unloadEventEnd?: number;
unloadEventStart?: number;
}
type PerformanceTimingKeys =
'connectEnd' |
'connectStart' |
'domComplete' |
'domContentLoadedEventEnd' |
'domContentLoadedEventStart' |
'domInteractive' |
'domainLookupEnd' |
'domainLookupStart' |
'fetchStart' |
'loadEventEnd' |
'loadEventStart' |
'redirectEnd' |
'redirectStart' |
'requestStart' |
'responseEnd' |
'responseStart' |
'secureConnectionStart' |
'unloadEventEnd' |
'unloadEventStart';
const afterLoad = (callback: () => void) => {
if (document.readyState === 'complete') {
// Queue a task so the callback runs after `loadEventEnd`.
setTimeout(callback, 0);
} else {
// Use `pageshow` so the callback runs after `loadEventEnd`.
addEventListener('pageshow', callback);
}
}
const getNavigationEntryFromPerformanceTiming = () => {
// Really annoying that TypeScript errors when using `PerformanceTiming`.
const timing = performance.timing;
const navigationEntry: NavigationEntryShim = {
entryType: 'navigation',
startTime: 0,
};
for (const key in timing) {
if (key !== 'navigationStart' && key !== 'toJSON') {
navigationEntry[key as PerformanceTimingKeys] = Math.max(
timing[key as PerformanceTimingKeys] - timing.navigationStart, 0);
}
}
return navigationEntry as PerformanceNavigationTiming;
};
export const getTTFB = (onReport: ReportHandler) => {
const metric = initMetric('TTFB');
afterLoad(() => {
try {
// Use the NavigationTiming L2 entry if available.
const navigationEntry = performance.getEntriesByType('navigation')[0] ||
getNavigationEntryFromPerformanceTiming();
metric.value = metric.delta =
(navigationEntry as PerformanceNavigationTiming).responseStart;
metric.entries = [navigationEntry];
metric.isFinal = true;
onReport(metric);
} catch (error) {
// Do nothing.
}
});
};

23
node_modules/web-vitals/src/index.ts generated vendored Normal file
View file

@ -0,0 +1,23 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {getCLS} from './getCLS.js';
export {getFCP} from './getFCP.js';
export {getFID} from './getFID.js';
export {getLCP} from './getLCP.js';
export {getTTFB} from './getTTFB.js';
export * from './types.js';

48
node_modules/web-vitals/src/lib/bindReporter.ts generated vendored Normal file
View file

@ -0,0 +1,48 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Metric, ReportHandler} from '../types.js';
export const bindReporter = (
callback: ReportHandler,
metric: Metric,
po: PerformanceObserver | undefined,
observeAllUpdates?: boolean,
) => {
let prevValue: number;
return () => {
if (po && metric.isFinal) {
po.disconnect();
}
if (metric.value >= 0) {
if (observeAllUpdates ||
metric.isFinal ||
document.visibilityState === 'hidden') {
metric.delta = metric.value - (prevValue || 0);
// Report the metric if there's a non-zero delta, if the metric is
// final, or if no previous value exists (which can happen in the case
// of the document becoming hidden when the metric value is 0).
// See: https://github.com/GoogleChrome/web-vitals/issues/14
if (metric.delta || metric.isFinal || prevValue === undefined) {
callback(metric);
prevValue = metric.value;
}
}
}
}
}

24
node_modules/web-vitals/src/lib/generateUniqueID.ts generated vendored Normal file
View file

@ -0,0 +1,24 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Performantly generate a unique, 27-char string by combining the current
* timestamp with a 13-digit random number.
* @return {string}
*/
export const generateUniqueID = () => {
return `${Date.now()}-${Math.floor(Math.random() * (9e12 - 1)) + 1e12}`;
};

39
node_modules/web-vitals/src/lib/getFirstHidden.ts generated vendored Normal file
View file

@ -0,0 +1,39 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {onHidden} from './onHidden.js';
let firstHiddenTime: number
export const getFirstHidden = () => {
if (firstHiddenTime === undefined) {
// If the document is hidden when this code runs, assume it was hidden
// since navigation start. This isn't a perfect heuristic, but it's the
// best we can do until an API is available to support querying past
// visibilityState.
firstHiddenTime = document.visibilityState === 'hidden' ? 0 : Infinity;
// Update the time if/when the document becomes hidden.
onHidden(({timeStamp}) => firstHiddenTime = timeStamp, true);
}
return {
get timeStamp() {
return firstHiddenTime;
}
}
};

30
node_modules/web-vitals/src/lib/initMetric.ts generated vendored Normal file
View file

@ -0,0 +1,30 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Metric} from '../types.js';
import {generateUniqueID} from './generateUniqueID.js';
export const initMetric = (name: Metric['name'], value = -1): Metric => {
return {
name,
value,
delta: 0,
entries: [],
id: generateUniqueID(),
isFinal: false
};
};

45
node_modules/web-vitals/src/lib/observe.ts generated vendored Normal file
View file

@ -0,0 +1,45 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface PerformanceEntryHandler {
(entry: PerformanceEntry): void;
}
/**
* Takes a performance entry type and a callback function, and creates a
* `PerformanceObserver` instance that will observe the specified entry type
* with buffering enabled and call the callback _for each entry_.
*
* This function also feature-detects entry support and wraps the logic in a
* try/catch to avoid errors in unsupporting browsers.
*/
export const observe = (
type: string,
callback: PerformanceEntryHandler,
): PerformanceObserver | undefined => {
try {
if (PerformanceObserver.supportedEntryTypes.includes(type)) {
const po: PerformanceObserver =
new PerformanceObserver((l) => l.getEntries().map(callback));
po.observe({type, buffered: true});
return po;
}
} catch (e) {
// Do nothing.
}
return;
};

50
node_modules/web-vitals/src/lib/onHidden.ts generated vendored Normal file
View file

@ -0,0 +1,50 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface OnHiddenCallback {
// TODO(philipwalton): add `isPersisted` if needed for bfcache.
({timeStamp, isUnloading}: {timeStamp: number; isUnloading: boolean}): void;
}
let isUnloading = false;
let listenersAdded = false;
const onPageHide = (event: PageTransitionEvent) => {
isUnloading = !event.persisted;
};
const addListeners = () => {
addEventListener('pagehide', onPageHide);
// `beforeunload` is needed to fix this bug:
// https://bugs.chromium.org/p/chromium/issues/detail?id=987409
// eslint-disable-next-line @typescript-eslint/no-empty-function
addEventListener('beforeunload', () => {});
}
export const onHidden = (cb: OnHiddenCallback, once = false) => {
if (!listenersAdded) {
addListeners();
listenersAdded = true;
}
addEventListener('visibilitychange', ({timeStamp}) => {
if (document.visibilityState === 'hidden') {
cb({timeStamp, isUnloading});
}
}, {capture: true, once});
};

32
node_modules/web-vitals/src/lib/whenInput.ts generated vendored Normal file
View file

@ -0,0 +1,32 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let inputPromise: Promise<Event>;
export const whenInput = () => {
if (!inputPromise) {
inputPromise = new Promise((r) => {
return ['scroll', 'keydown', 'pointerdown'].map((type) => {
addEventListener(type, r, {
once: true,
passive: true,
capture: true,
});
});
});
}
return inputPromise;
};

45
node_modules/web-vitals/src/types.ts generated vendored Normal file
View file

@ -0,0 +1,45 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Metric {
// The name of the metric (in acronym form).
name: 'CLS' | 'FCP' | 'FID' | 'LCP' | 'TTFB';
// The current value of the metric.
value: number;
// The delta between the current value and the last-reported value.
// On the first report, `delta` and `value` will always be the same.
delta: number;
// A unique ID representing this particular metric that's specific to the
// current page. This ID can be used by an analytics tool to dedupe
// multiple values sent for the same metric, or to group multiple deltas
// together and calculate a total.
id: string;
// `false` if the value of the metric may change in the future,
// for the current page.
isFinal: boolean;
// Any performance entries used in the metric value calculation.
// Note, entries will be added to the array as the value changes.
entries: PerformanceEntry[];
}
export interface ReportHandler {
(metric: Metric): void;
}