UNPKG

@zendesk/react-measure-timing-hooks

Version:

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

371 lines (326 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 } }