UNPKG

@zendesk/react-measure-timing-hooks

Version:

react hooks for measuring time to interactive and time to render of components

240 lines (239 loc) 12.3 kB
/** * Copyright Zendesk, Inc. * * Use of this source code is governed under the Apache License, Version 2.0 * found at http://www.apache.org/licenses/LICENSE-2.0. */ import type { DependencyList } from 'react'; import type { ActionLog } from './ActionLog'; import type { ActionLogCache } from './ActionLogCache'; import type { ACTION_TYPE, MARKER } from './constants'; export type ReportFnV1<Metadata extends Record<string, unknown>> = (reportArgs: ReportArguments<Metadata>) => void; export type ShouldResetOnDependencyChange = (oldDependencies: DependencyList, newDependencies: DependencyList) => boolean; export interface WithTimingId { /** * The identifier of this timing. Needs to be the same across all usages. * You may change it during the hook execution without restarting the measurement, however it's best to avoid that. * If you want an 'id' change to restart measurement, put it into hook's dependencies list. */ id: string; } export interface WithOnInternalError<CustomMetadata extends Record<string, unknown>> { onInternalError?: (error: Error, reportWithMetadata?: ReportArguments<CustomMetadata>) => void; } export interface WithBeaconConfig<Placements extends string = string> { /** * Where is this beacon placed? * This value must be unique between all the instances of the hook, so they co-operate correctly. */ placement: Placements; } export interface WithActionLog<CustomMetadata extends Record<string, unknown>> { /** * An instance of ActionLog in a ref object (must be provided so it can be cached externally) */ actionLog: ActionLog<CustomMetadata>; } export interface WithShouldResetOnDependencyChangeFn { /** * An optional function that returns true if you want to restart measuring after dependencies have changed. * You may use this to prevent restarting in case you know a component re-renders with an invalid value. * Signature is: (oldDependencies, newDependencies): boolean * */ shouldResetOnDependencyChangeFn?: ShouldResetOnDependencyChange; } export interface WithMeasurementBeaconState extends WithShouldResetOnDependencyChangeFn { /** * Time different rendering stages by changing the value of this property. * Use one of the DEFAULT_STAGES or any string value for custom stages. * Must be used in combination with `finalStages`, or left undefined to disable capturing stages. */ stage?: string; /** * Setting this to anything other than undefined or null * will immediately set the stage to DEFAULT_STAGES.ERROR, and flush any report information */ error?: Error | null; /** * Setting this to `false` will immediately disable capturing any data. * If flushUponDeactivation is `true` and data is present, it will immediately flush any report information. * The equivalent of setting `stage` to INFORMATIVE_STAGES.INACTIVE. */ isActive?: boolean; } export interface StaticActionLogOptions<Placements extends string, CustomMetadata extends Record<string, unknown>> extends WithReportFn<CustomMetadata>, WithOnInternalError<CustomMetadata> { /** the number of milliseconds we will wait before another render, or sending the report */ debounceMs?: number; /** stop timing after this amount of milliseconds passes from the start of the render */ timeoutMs?: number; /** * When one of these stages is reached and debounced time passes, we will send the report. * If an empty array (default), the report will be sent immediately after rendering settles. * */ finalStages?: readonly string[]; /** * List all the stages that will be considered as "loading stages". * Used only for the reportFn. Does not affect other functionality. * */ loadingStages?: readonly string[]; /** * When one of these stages is reached, we will IMMEDIATELY send the report and reset. * This always includes the error stages. * */ immediateSendReportStages?: readonly string[]; /** * Will only activate the timing hook after *all* the placements listed in the array were mounted * Specifying this can be useful if you have conditional logic that means only some hooks will get mounted (e.g. timing of opening a menu). * */ waitForBeaconActivation?: readonly Placements[]; /** * Require a certain number of simultaneously mounted placements to send the timing report. */ minimumExpectedSimultaneousBeacons?: number; /** * If true, will flush the report immediately upon deactivation, without waiting for browser to settle. * Default is `false`. */ flushUponDeactivation?: boolean; } export interface WithReportFn<CustomMetadata extends Record<string, unknown>> { reportFn?: ReportFnV1<CustomMetadata>; /** * Will fire any time an action is added to the action log. */ onActionAddedCallback?: (action: ActionLog<CustomMetadata>) => void; } export interface WithMetadata<Metadata extends Record<string, unknown>> { metadata?: Metadata; } export interface ReportArguments<CustomMetadata extends Record<string, unknown>> extends Pick<StaticActionLogOptions<string, CustomMetadata>, 'finalStages' | 'loadingStages' | 'immediateSendReportStages'> { readonly actions: readonly ActionWithStateMetadata[]; readonly timingId?: string; readonly isFirstLoad?: boolean; readonly maximumActiveBeaconsCount?: number; readonly minimumExpectedSimultaneousBeacons?: number; readonly flushReason?: string; readonly metadata?: CustomMetadata; readonly measures: { tti?: PerformanceMeasure; ttr?: PerformanceMeasure; }; } export interface DynamicActionLogOptions<CustomMetadata extends Record<string, unknown>> extends WithTimingId, WithShouldResetOnDependencyChangeFn, WithReportFn<CustomMetadata>, WithOnInternalError<CustomMetadata> { } export interface WithActionLogCache<CustomMetadata extends Record<string, unknown>> { actionLogCache: ActionLogCache<CustomMetadata>; } export interface ActionLogExternalApi<CustomMetadata extends Record<string, unknown> = Record<string, unknown>> { markRenderStart: (idSuffix: string) => void; markRenderEnd: (idSuffix: string) => void; dispose: (idSuffix: string) => void; getActionLogForId: (idSuffix: string) => ActionLog<CustomMetadata>; getActionLogForIdIfExists: (idSuffix: string) => ActionLog<CustomMetadata> | undefined; setActive: (idSuffix: string, active: boolean) => void; markStage: (idSuffix: string, stage: string, stageMetadata?: Record<string, unknown>) => void; clear: (idSuffix: string) => void; setMetadata: (idSuffix: string, metadata: CustomMetadata) => void; } export interface WithGarbageCollectMs { garbageCollectMs: number; } export interface UseActionLogCacheOptions<CustomMetadata extends Record<string, unknown>, Placements extends string = string> extends StaticActionLogOptions<Placements, CustomMetadata>, WithGarbageCollectMs { } export interface UseActionLogOptions<CustomMetadata extends Record<string, unknown>, Placements extends string = string> extends WithTimingId, StaticActionLogOptions<Placements, CustomMetadata>, Partial<WithGarbageCollectMs>, WithActionLogCache<CustomMetadata> { } export interface UseTimingMeasurementHookConfiguration<CustomMetadata extends Record<string, unknown>> extends DynamicActionLogOptions<CustomMetadata>, WithBeaconConfig, WithMeasurementBeaconState, WithActionLog<CustomMetadata> { } export interface UseTimingHookConfiguration<CustomMetadata extends Record<string, unknown>> extends DynamicActionLogOptions<CustomMetadata>, WithBeaconConfig, WithMeasurementBeaconState, WithMetadata<CustomMetadata>, UseActionLogOptions<CustomMetadata> { } export interface WithIdSuffix { idSuffix?: string; } export interface UseTimingBeaconHookOptions<CustomMetadata extends Record<string, unknown>> extends Omit<DynamicActionLogOptions<CustomMetadata>, 'id'>, WithMeasurementBeaconState, WithBeaconConfig, WithMetadata<CustomMetadata>, WithIdSuffix { } export interface GeneratedUseTimingBeaconHookConfiguration<CustomMetadata extends Record<string, unknown>> extends Omit<UseTimingBeaconHookOptions<CustomMetadata>, 'placement' | 'type'> { } export type GeneratedUseTimingBeaconHook = <CustomMetadata extends Record<string, unknown>>(options?: GeneratedUseTimingBeaconHookConfiguration<CustomMetadata>, restartWhenChanged?: DependencyList) => void; export interface GenerateUseTimingHooksConfiguration<Name extends string, Placements extends string, CustomMetadata extends Record<string, unknown>> extends Partial<UseActionLogOptions<CustomMetadata, Placements>>, WithIdPrefix { name: Name; } export interface WithIdPrefix { idPrefix: string; } export interface GetPrefixedUseTimingHooksConfiguration<Placements extends string, Metadata extends Record<string, unknown>> extends Omit<UseActionLogOptions<Metadata, Placements>, 'id'>, WithIdPrefix, WithBeaconConfig<Placements> { } export interface GetExternalApiConfiguration<Placements extends string, Metadata extends Record<string, unknown>> extends WithActionLogCache<Metadata>, WithIdPrefix, WithBeaconConfig<Placements> { } export type GeneratedTimingHooks<Name extends string, Placements extends readonly string[], Metadata extends Record<string, unknown> = Record<string, unknown>, GeneratedUseTimingBeacon = GeneratedUseTimingBeaconHook> = { [K in `use${Name}TimingIn${Placements[number]}`]: GeneratedUseTimingBeacon; } & { [K in `imperative${Placements[number]}TimingApi`]: ActionLogExternalApi<Metadata>; } & { actionLogCache: ActionLogCache<Metadata>; }; export type ActionType = (typeof ACTION_TYPE)[keyof typeof ACTION_TYPE]; export type Marker = (typeof MARKER)[keyof typeof MARKER]; export type SpanMarker = (typeof MARKER)['START' | 'END']; export type PointMarker = (typeof MARKER)['POINT']; export type CustomPerformanceEntry = PerformanceEntry & { startMark?: PerformanceEntry; endMark?: PerformanceEntry; }; export interface BaseAction<NameT extends ActionType, MarkerT extends Marker> { timestamp: number; entry: CustomPerformanceEntry; type: NameT; marker: MarkerT; source: string; metadata?: Record<string, unknown>; } export type RenderActionType = (typeof ACTION_TYPE)['RENDER']; export type RenderAction = BaseAction<RenderActionType, SpanMarker>; export type UnresponsiveActionType = (typeof ACTION_TYPE)['UNRESPONSIVE']; export type UnresponsiveAction = BaseAction<UnresponsiveActionType, SpanMarker>; export type SpanAction = RenderAction | UnresponsiveAction; export type StageChangeActionType = (typeof ACTION_TYPE)['STAGE_CHANGE']; export interface StageChangeAction extends BaseAction<StageChangeActionType, PointMarker> { stage: string; renderEntry?: CustomPerformanceEntry; } export type DependencyChangeActionType = (typeof ACTION_TYPE)['DEPENDENCY_CHANGE']; export type DependencyChangeAction = BaseAction<DependencyChangeActionType, PointMarker>; export type Action = SpanAction | StageChangeAction | DependencyChangeAction; export interface StateMeta { mountedPlacements: readonly string[]; timingId: string; } export type ActionWithStateMetadata = Action & StateMeta; export interface StageDescription extends StateMeta { type: ActionType; source: string; previousStage: string; stage: string; timeToStage: number; /** relative timestamp of previous stage */ previousStageTimestamp: number; /** relative timestamp (ms since the beginning of the first action) */ timestamp: number; metadata?: Record<string, unknown>; dependencyChanges: number; } export interface TimingSpan { type: ActionType | 'ttr' | 'tti'; description: string; /** absolute timestamp of when render begun */ startTime: number; /** absolute timestamp of when render ended (ms since the beginning of the first action) */ endTime: number; /** relative endTime (ms since the beginning of the first action) */ relativeEndTime: number; entry?: PerformanceEntry; data: StateMeta & { source?: string; metadata?: Record<string, unknown>; dependencyChanges?: number; stage: string; previousStage?: string; timeToStage?: number; }; }