perfume.js

A flexible JavaScript library for measuring First Contentful Paint (FP/FCP), First Input Delay (FID) and components lifecycle performance (eg. Angular, React). Report real user measurements to Google Analytics or your ideal tracking tool.

Github stars Tracking Chart

Perfume.js v4.7.1

NPM version Build Status NPM Downloads Test Coverage JS gzip size

Perfume is a tiny, web performance monitoring library which reports field data like Navigation Timing, Resource Timing, First Contentful Paint (FP/FCP), Largest Contentful Paint (LCP), First Input Delay (FID) back to your favorite analytics tool.

English, 简体中文

Why Perfume.js?

  • ⏰ Supported latest Performance APIs for precise metrics
  • ? Cross browser tested
  • ? Filters out false positive/negative results
  • ? Only 2Kb gzip
  • ? Flexible analytics tool
  • ⚡️ Waste-zero ms with requestIdleCallback strategy built-in

The latest in metrics & Real User Measurement

Perfume leverage the latest W3C Performance Drafts (e.g. PerformanceObserver), for measuring performance that matters! Also known as field data, they allow to understand what real-world users are actually experiencing.

  • Navigation Timing
  • Navigator Interface
  • Resource Timing
  • First Paint (FP)
  • First Contentful Paint (FCP)
  • Largest Contentful Paint (LCP)
  • First Input Delay (FID)
  • Framework components lifecycle monitoring

First Paint and First Input Delay

First Contentful Paint

Installing

npm (https://www.npmjs.com/package/perfume.js):

npm install perfume.js --save

Importing library

You can import the generated bundle to use the whole library generated:

import Perfume from 'perfume.js';

Universal Module Definition:

import Perfume from 'node_modules/perfume.js/dist/perfume.umd.min.js';

Quick start

Metrics like Navigation Timing, Network Information, FP, FCP, FID, and LCP are default reported with Perfume; All results will be reported to the analyticsTracker callback, and the code below is just one way on how you can organize your tracking, feel free to tweak it as you prefer.

const perfume = new Perfume({
  analyticsTracker: (options) => {
    const { metricName, data, duration } = options;
    switch (metricName) {
      case 'navigationTiming':
        if (data && data.timeToFirstByte) {
          myAnalyticsTool.track('navigationTiming', data);
        }
        break;
      case 'networkInformation':
        if (data && data.effectiveType) {
          myAnalyticsTool.track('networkInformation', data);
        }
        break;
      case 'firstPaint':
        myAnalyticsTool.track('firstPaint', { duration });
        break;
      case 'firstContentfulPaint':
        myAnalyticsTool.track('firstContentfulPaint', { duration });
        break;
      case 'firstInputDelay':
        myAnalyticsTool.track('firstInputDelay', { duration });
        break;
      case 'largestContentfulPaint':
        myAnalyticsTool.track('largestContentfulPaint', { duration });
        break;
      default:
        break;
    }
  },
  logging: false,
  maxMeasureTime: 10000,
});

APIs

Coo coo coo cool, let's learn something new.

Navigation Timing collects performance metrics for the life and timings of a network request.
Perfume helps expose some of the key metrics you might need.

Navigation Timing is run by default.

const perfume = new Perfume({
  analyticsTracker: ({ metricName, data }) => {
    myAnalyticsTool.track(metricName, data);
  })
});
// Perfume.js: NavigationTiming {{'{'}} ... timeToFirstByte: 192.65 {{'}'}}

Resource Timing

Resource Timing collects performance metrics for document-dependent resources. Stuff like style sheets, scripts, images, et cetera.
Perfume helps expose all PerformanceResourceTiming entries and group data data consumption by Kb used.

const perfume = new Perfume({
  resourceTiming: true,
  dataConsumption: true,
  analyticsTracker: ({ metricName, data }) => {
    myAnalyticsTool.track(metricName, data);
  })
});
// Perfume.js: dataConsumption { "css": 185.95, "fetch": 0, "img": 377.93, ... , "script": 8344.95 }

First Paint (FP)

FP is the exact time the browser renders anything as visually different from what was on the screen before navigation, e.g. a background change after a long blank white screen time.

First Paint is run by default.

const perfume = new Perfume({
  analyticsTracker: ({ metricName, duration }) => {
    myAnalyticsTool.track(metricName, duration);
  })
});
// Perfume.js: First Paint 1482.00 ms

First Contentful Paint (FCP)

FCP is the exact time the browser renders the first bit of content from the DOM, which can be anything from an important image, text, or even the small SVG at the bottom of the page.

First Contentful Paint is run by default.

const perfume = new Perfume({
  analyticsTracker: ({ metricName, duration }) => {
    myAnalyticsTool.track(metricName, duration);
  })
});
// Perfume.js: First Contentful Paint 2029.00 ms

Largest Contentful Paint (LCP)

Largest Contentful Paint (LCP) is an important, user-centric metric for measuring
perceived load speed because it marks the point in the page load timeline when the page's main
content has likely loaded—a fast LCP helps reassure the user that the page is useful.

const perfume = new Perfume({
  analyticsTracker: ({ metricName, duration }) => {
    myAnalyticsTool.track(metricName, duration);
  })
});
// Perfume.js: Largest Contentful Paint 2429.00 ms

First Input Delay (FID)

FID measures the time from when a user first interacts with your site (i.e. when they click a link, tap on a button) to the time when the browser is actually able to respond to that interaction.

First Input Delay is run by default.

const perfume = new Perfume({
  analyticsTracker: ({ metricName, duration }) => {
    myAnalyticsTool.track(metricName, duration);
  })
});
// Perfume.js: First Input Delay 3.20 ms

Annotate metrics in the DevTools

Performance.mark (User Timing API) is used to create an application-defined peformance entry in the browser's performance entry buffer.

const perfume = new Perfume({
  analyticsTracker: ({ metricName, duration }) => {
    myAnalyticsTool.track(metricName, duration);
  })
});
perfume.start('fibonacci');
fibonacci(400);
perfume.end('fibonacci');
// Perfume.js: fibonacci 0.14 ms

Performance Mark

Component First Paint

This metric mark the point, immediately after creating a new component, when the browser renders pixels to the screen.

const perfume = new Perfume({
  analyticsTracker: ({ metricName, duration }) => {
    myAnalyticsTool.track(metricName, duration);
  })
});
perfume.start('togglePopover');
$(element).popover('toggle');
perfume.endPaint('togglePopover');
// Perfume.js: togglePopover 10.54 ms

Performance

Custom Logging

Save the duration and print it out exactly the way you want it.

const perfume = new Perfume({
  analyticsTracker: ({ metricName, duration }) => {
    myAnalyticsTool.track(metricName, duration);
  }),
  logPrefix: '? HayesValley.js:'
});
perfume.start('fibonacci');
fibonacci(400);
const duration = perfume.end('fibonacci');
// ? HayesValley.js: Custom logging 0.14 ms

Frameworks

Angular

Wth the Angular framework, we can start configuring Perfume to collect the initial performance metrics (eg. FCP, FID). Make sure to import the PefumeModule at first inside the NgModule to let the PerformanceObserver work correctly.

In a large application use the @PerfumeAfterViewInit() decorator to monitor the rendering performance of the most complex components. Avoid using it inside a NgFor, instead focus on components that include a collection of smaller components.

The NgPerfume service exposes all the methods and property of the perfume instance, you can annotate component lifecycles combined with APIs calls to measure how long it takes to paint the component.

import { NgPerfume, PerfumeModule, PerfumeAfterViewInit } from 'perfume.js/angular';
import { AppComponent } from './app.component';
import { AppApi } from './app-api';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.less'],
})
@PerfumeAfterViewInit('AppComponent')
export class AppComponent implements AfterViewInit {
  data: AwesomeType;

  constructor(public perfume: NgPerfume) {
    // Start measure component time to paint
    this.perfume.start('AppComponentAfterPaint');
  }

  ngAfterViewInit() {
    this.loadAwesomeData();
  }

  loadAwesomeData = async () => {
    await AppApi.loadAmazingData();
    this.data = AppApi.loadAwesomeData();
    // End measure component time to paint
    this.perfume.endPaint('AppComponentAfterPaint');
  }
}

// Perfume.js config, supports AOT and DI
const analyticsTracker = function ({ metricName, data, duration }) {
  switch(metricName) {
    case 'navigationTiming':
      myAnalyticsTool.track(metricName, data);
      break;
    case 'resourceTiming':
      myAnalyticsTool.track(metricName, data);
      break;
    case 'dataConsumption':
      myAnalyticsTool.track(metricName, data);
      break;
    default:
      myAnalyticsTool.track(metricName, duration);
      break;
  }
})
export const PerfumeConfig = {
  dataConsumption: true,
  resourceTiming: true,
  analyticsTracker,
};

@NgModule({
  declarations: [AppComponent],
  imports: [PerfumeModule.forRoot(PerfumeConfig), BrowserModule],
  bootstrap: [AppComponent],
})
export class AppModule {}

Angular Performance Decorator

React

In combination with the React framework, we can start configuring Perfume to collect the initial performance metrics (eg. FCP, FID).

Use perfume.start() and perfume.endPaint() into a component lifecycle mixed with APIs calls to measure how long it takes to paint the component.

import React from 'react';
import Perfume from 'perfume.js';

import { AppApi } from './AppApi';

const analyticsTracker = function ({ metricName, data, duration }) {
  switch(metricName) {
    case 'navigationTiming':
      myAnalyticsTool.track(metricName, data);
      break;
    case 'resourceTiming':
      myAnalyticsTool.track(metricName, data);
      break;
    case 'dataConsumption':
      myAnalyticsTool.track(metricName, data);
      break;
    default:
      myAnalyticsTool.track(metricName, duration);
      break;
  }
})

const perfume = new Perfume({
  dataConsumption: true,
  resourceTiming: true,
  analyticsTracker,
});

export default class App extends React.Component {

  constructor() {
    // Start measure component time to paint
    perfume.start('AppAfterPaint');
  }

  loadData = async () => {
    await AppApi.loadAmazingData();
    await AppApi.loadAwesomeData();
    // End measure component time to paint
    perfume.endPaint('AppAfterPaint');
  }

  render() {
    const data = this.loadData();
    return (
      <div>
        <h2>Awesome App</h2>
        <div>{data}</div>
      </div>
    );
  }
}

Analytics

Configurable analytics callback to use Perfume.js with any platform.

const perfume = new Perfume({
  analyticsTracker: ({ metricName, data, duration }) => {
    myAnalyticsTool.track(metricName, data, duration);
  })
});

Customize & Utilities

Default Options

Default options provided to Perfume.js constructor.

const options = {
  // Metrics
  dataConsumption: false,
  resourceTiming: false,
  // Analytics
  analyticsTracker: options => {},
  // Logging
  logPrefix: "Perfume.js:"
  logging: true,
  maxMeasureTime: 15000,
};

Develop

  • npm run test: Run test suite
  • npm run build: Generate bundles and typings
  • npm run lint: Lints code

Articles

Plugins

Perfume is used by

Credits and Specs

Made with ☕️ by @zizzamia and
I want to thank some friends and projects for the work they did:

Contributors

This project exists thanks to all the people who contribute.

Backers

Thank you to all our backers! ? [Become a backer]

Code and documentation copyright 2020 Leonardo Zizzamia. Code released under the MIT license. Docs released under Creative Commons.

Team

Main metrics

Overview
Name With OwnerZizzamia/perfume.js
Primary LanguageTypeScript
Program languageTypeScript (Language Count: 2)
Platform
License:MIT License
所有者活动
Created At2017-12-09 20:38:16
Pushed At2024-08-16 16:08:16
Last Commit At2024-06-03 21:21:03
Release Count76
Last Release Namev9.4.0 (Posted on )
First Release Namev0.2.0 (Posted on )
用户参与
Stargazers Count3.2k
Watchers Count23
Fork Count110
Commits Count625
Has Issues Enabled
Issues Count127
Issue Open Count11
Pull Requests Count101
Pull Requests Open Count5
Pull Requests Close Count32
项目设置
Has Wiki Enabled
Is Archived
Is Fork
Is Locked
Is Mirror
Is Private