From bc0e782fd14ee1246588b013f76f11a7bdeb8ffd Mon Sep 17 00:00:00 2001 From: Leonardo Zizzamia Date: Thu, 23 Apr 2020 00:07:03 -0700 Subject: [PATCH] refactor: reduced library kb --- README.md | 5 +- __tests__/config.spec.ts | 9 ++++ __tests__/log.ts | 25 ++++++++++ __tests__/perfume.spec.ts | 86 +++++++-------------------------- __tests__/utils.spec.ts | 21 ++++++++ docs/src/app/app.component.html | 4 +- docs/src/app/perfume.ts | 2 - package.json | 4 +- src/config.ts | 10 ++++ src/log.ts | 13 +++++ src/perfume.ts | 76 +++++++++-------------------- src/types.ts | 2 - src/utils.ts | 12 +++++ 13 files changed, 135 insertions(+), 134 deletions(-) create mode 100644 __tests__/config.spec.ts create mode 100644 __tests__/log.ts create mode 100644 __tests__/utils.spec.ts create mode 100644 src/config.ts create mode 100644 src/log.ts create mode 100644 src/utils.ts diff --git a/README.md b/README.md index 8c6c80c..9311c9b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -# [Perfume.js v5.0.0-rc.6](http://perfumejs.com) +# [Perfume.js v5.0.0-rc.7](http://perfumejs.com) [![NPM version](https://badge.fury.io/js/perfume.js.svg)](https://www.npmjs.org/package/perfume.js) [![Build Status](https://travis-ci.org/Zizzamia/perfume.js.svg?branch=master)](https://travis-ci.org/Zizzamia/perfume.js) [![NPM Downloads](http://img.shields.io/npm/dm/perfume.js.svg)](https://www.npmjs.org/package/perfume.js) [![Test Coverage](https://api.codeclimate.com/v1/badges/f813d2f45b274d93b8c5/test_coverage)](https://codeclimate.com/github/Zizzamia/perfume.js/test_coverage) [![JS gzip size](https://img.badgesize.io/https://unpkg.com/perfume.js?compression=gzip&label=JS+gzip+size)](https://unpkg.com/perfume.js) @@ -223,7 +223,6 @@ Perfume helps expose all PerformanceResourceTiming entries and group data data c ```javascript const perfume = new Perfume({ resourceTiming: true, - dataConsumption: true, analyticsTracker: ({ metricName, data }) => { myAnalyticsTool.track(metricName, data); }) @@ -275,8 +274,6 @@ Default options provided to Perfume.js constructor. ```javascript const options = { - // Metrics - dataConsumption: false, resourceTiming: false, // Analytics analyticsTracker: options => {}, diff --git a/__tests__/config.spec.ts b/__tests__/config.spec.ts new file mode 100644 index 0000000..fb09ee2 --- /dev/null +++ b/__tests__/config.spec.ts @@ -0,0 +1,9 @@ +import { config } from '../src/config'; + +describe('config', () => { + it('should be defined', () => { + expect(config.logPrefix).toEqual('Perfume.js:'); + expect(config.logging).toEqual(true); + expect(config.maxMeasureTime).toEqual(15000); + }); +}); \ No newline at end of file diff --git a/__tests__/log.ts b/__tests__/log.ts new file mode 100644 index 0000000..0fca811 --- /dev/null +++ b/__tests__/log.ts @@ -0,0 +1,25 @@ +import { config } from '../src/config'; +import { logWarn } from '../src/log'; + +describe('log', () => { + let spy; + + describe('.logWarn()', () => { + it('should throw a console.warn if config.warning is true', () => { + spy = jest.spyOn(window.console, 'warn'); + logWarn('message'); + expect(spy.mock.calls.length).toEqual(1); + expect(spy).toHaveBeenCalledWith(config.logPrefix, 'message'); + }); + + /** + it('should not throw a console.warn if config.logging is false', () => { + spy = jest.spyOn(window.console, 'warn'); + config.logging = false; + logWarn('message'); + expect(spy.mock.calls.length).toEqual(0); + expect(spy).not.toHaveBeenCalled(); + }); + */ + }); +}); diff --git a/__tests__/perfume.spec.ts b/__tests__/perfume.spec.ts index 17b0720..d294d17 100644 --- a/__tests__/perfume.spec.ts +++ b/__tests__/perfume.spec.ts @@ -1,4 +1,6 @@ +import { config } from '../src/config'; import { C, W, WN, WP } from '../src/constants'; +import * as log from '../src/log'; import Perfume from '../src/perfume'; import mock from './_mock'; @@ -23,35 +25,16 @@ describe('Perfume', () => { } }); - describe('config', () => { - const instance = new Perfume(); - - it('should be defined', () => { - expect(instance.config.dataConsumption).toEqual(false); - expect(instance.config.resourceTiming).toEqual(false); - expect(instance.config.logPrefix).toEqual('Perfume.js:'); - expect(instance.config.logging).toEqual(true); - expect(instance.config.maxMeasureTime).toEqual(15000); - }); - }); - describe('constructor', () => { it('should run with config version A', () => { new Perfume({ - dataConsumption: true, + resourceTiming: true, }); }); it('should run with config version B', () => { new Perfume({ - dataConsumption: true, resourceTiming: true, - }); - }); - - it('should run with config version C', () => { - new Perfume({ - dataConsumption: true, logging: false, }); }); @@ -70,7 +53,7 @@ describe('Perfume', () => { }); it('should throw a logWarn if recording already started', () => { - spy = jest.spyOn(perfume as any, 'logWarn'); + spy = jest.spyOn(log, 'logWarn'); perfume.start('metricName'); perfume.start('metricName'); expect(spy.mock.calls.length).toEqual(1); @@ -80,7 +63,7 @@ describe('Perfume', () => { describe('.end()', () => { it('should throw a logWarn if param is correct and recording already stopped', () => { - spy = jest.spyOn(perfume as any, 'logWarn'); + spy = jest.spyOn(log, 'logWarn'); perfume.end('metricName'); expect(spy.mock.calls.length).toEqual(1); expect(spy).toHaveBeenCalledWith('Recording already stopped.'); @@ -88,7 +71,7 @@ describe('Perfume', () => { it('should call log() with correct params', () => { spy = jest.spyOn(perfume as any, 'log'); - perfume.config.logging = true; + config.logging = true; perfume.start('metricName'); perfume.end('metricName'); expect(spy.mock.calls.length).toEqual(1); @@ -117,7 +100,7 @@ describe('Perfume', () => { it('should call sendTiming() with correct params', () => { spy = jest.spyOn(perfume as any, 'sendTiming'); - perfume.config.logging = true; + config.logging = true; perfume.start('metricName'); perfume.end('metricName'); expect(spy.mock.calls.length).toEqual(1); @@ -185,14 +168,14 @@ describe('Perfume', () => { describe('.log()', () => { it('should not call window.console.log() if logging is disabled', () => { - perfume.config.logging = false; + config.logging = false; spy = jest.spyOn(C, 'log'); (perfume as any).log({ metricName: '', data: '0 ms' }); expect(spy).not.toHaveBeenCalled(); }); it('should call window.console.log() if logging is enabled', () => { - perfume.config.logging = true; + config.logging = true; spy = jest.spyOn(C, 'log'); (perfume as any).log({ measureName: 'metricName', @@ -206,7 +189,7 @@ describe('Perfume', () => { }); it('should call window.console.log() if params are correct', () => { - perfume.config.logging = true; + config.logging = true; spy = jest.spyOn(C, 'log'); (perfume as any).log({ measureName: 'metricName', @@ -221,7 +204,7 @@ describe('Perfume', () => { it('should call window.console.log() with data', () => { const data = {}; - perfume.config.logging = true; + config.logging = true; spy = jest.spyOn(C, 'log'); (perfume as any).log({ measureName: 'metricName', @@ -445,7 +428,7 @@ describe('Perfume', () => { }); it('should call initResourceTiming when resourceTiming or is true', () => { - (perfume as any).config.resourceTiming = true; + config.resourceTiming = true; spy = jest.spyOn(perfume as any, 'initResourceTiming'); (perfume as any).initPerformanceObserver(); expect(spy.mock.calls.length).toEqual(1); @@ -455,7 +438,7 @@ describe('Perfume', () => { describe('.initResourceTiming()', () => { beforeEach(() => { - perfume.config.dataConsumption = true; + config.resourceTiming = true; (perfume as any).perfObservers.dataConsumption = { disconnect: () => {} }; }); @@ -536,23 +519,6 @@ describe('Perfume', () => { }); }); - describe('.logWarn()', () => { - it('should throw a console.warn if config.warning is true', () => { - spy = jest.spyOn(window.console, 'warn'); - (perfume as any).logWarn('message'); - expect(spy.mock.calls.length).toEqual(1); - expect(spy).toHaveBeenCalledWith(perfume.config.logPrefix, 'message'); - }); - - it('should not throw a console.warn if config.logging is false', () => { - spy = jest.spyOn(window.console, 'warn'); - perfume.config.logging = false; - (perfume as any).logWarn('message'); - expect(spy.mock.calls.length).toEqual(0); - expect(spy).not.toHaveBeenCalled(); - }); - }); - describe('.getNavigatorInfo()', () => { it('when navigator is not supported should return an empty object', () => { (WN as any) = undefined; @@ -616,7 +582,6 @@ describe('Perfume', () => { beforeEach(() => { perfume = new Perfume({ ...mock.defaultPerfumeConfig, - resourceTiming: true, }); }); @@ -626,7 +591,7 @@ describe('Perfume', () => { fetch: 0, total: 0, }; - perfume.config.dataConsumption = true; + config.resourceTiming = true; (perfume as any).perfObservers.dataConsumption = { disconnect: () => {} }; }); @@ -695,33 +660,18 @@ describe('Perfume', () => { }); }); - describe('.pushTask()', () => { - it('should call cb() if requestIdleCallback is undefined', () => { - spy = jest.fn(); - perfume['pushTask'](spy); - expect(spy.mock.calls.length).toEqual(1); - }); - - it('should call requestIdleCallback if is defined', () => { - spy = jest.fn(); - (W as any).requestIdleCallback = spy; - perfume['pushTask'](() => {}); - expect(spy.mock.calls.length).toEqual(1); - }); - }); - describe('.sendTiming()', () => { it('should not call analyticsTracker() if isHidden is true', () => { - perfume.config.analyticsTracker = () => {}; - spy = jest.spyOn(perfume.config, 'analyticsTracker'); + config.analyticsTracker = () => {}; + spy = jest.spyOn(config, 'analyticsTracker'); perfume['isHidden'] = true; (perfume as any).sendTiming({ measureName: 'metricName', data: 123 }); expect(spy).not.toHaveBeenCalled(); }); it('should call analyticsTracker() if analyticsTracker is defined', () => { - perfume.config.analyticsTracker = () => {}; - spy = jest.spyOn(perfume.config, 'analyticsTracker'); + config.analyticsTracker = () => {}; + spy = jest.spyOn(config, 'analyticsTracker'); (perfume as any).sendTiming({ measureName: 'metricName', data: 123 }); expect(spy.mock.calls.length).toEqual(1); expect(spy).toHaveBeenCalledWith({ diff --git a/__tests__/utils.spec.ts b/__tests__/utils.spec.ts new file mode 100644 index 0000000..ecc75a5 --- /dev/null +++ b/__tests__/utils.spec.ts @@ -0,0 +1,21 @@ +import { W } from '../src/constants'; +import { pushTask } from '../src/utils'; + +describe('utils', () => { + let spy; + + describe('.pushTask()', () => { + it('should call cb() if requestIdleCallback is undefined', () => { + spy = jest.fn(); + pushTask(spy); + expect(spy.mock.calls.length).toEqual(1); + }); + + it('should call requestIdleCallback if is defined', () => { + spy = jest.fn(); + (W as any).requestIdleCallback = spy; + pushTask(() => {}); + expect(spy.mock.calls.length).toEqual(1); + }); + }); +}); diff --git a/docs/src/app/app.component.html b/docs/src/app/app.component.html index 879cff5..3762675 100644 --- a/docs/src/app/app.component.html +++ b/docs/src/app/app.component.html @@ -277,7 +277,7 @@

- Resource Timing (experimental) + Resource Timing

Resource Timing collects performance metrics for document-dependent resources. Stuff like style sheets, scripts, images, et cetera.

@@ -292,7 +292,6 @@

const perfume = new Perfume({{'{'}}
   resourceTiming: true,
-  dataConsumption: true,
   analyticsTracker: ({{'{'}} metricName, data {{'}'}}) => {{'{'}}
     myAnalyticsTool.track(metricName, data);
   {{'}'}})
@@ -361,7 +360,6 @@ 

const options = {{'{'}}
   // Metrics
-  dataConsumption: false,
   resourceTiming: false,
   // Analytics
   analyticsTracker: options => {{'{}'}},
diff --git a/docs/src/app/perfume.ts b/docs/src/app/perfume.ts
index 6cbb841..010a908 100644
--- a/docs/src/app/perfume.ts
+++ b/docs/src/app/perfume.ts
@@ -77,9 +77,7 @@ export function analyticsTracker(options) {
 }
 
 export const PerfumeConfig = {
-  cumulativeLayoutShift: true,
   dataConsumption: true,
-  resourceTiming: true,
   analyticsTracker,
 };
 
diff --git a/package.json b/package.json
index 5a4a6c4..6ef0f60 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "perfume.js",
-  "version": "5.0.0-rc.6",
+  "version": "5.0.0-rc.7",
   "description": "Tiny web performance monitoring library which reports field data back to your favorite analytics tool.",
   "keywords": [
     "performance",
@@ -90,7 +90,7 @@
       "global": {
         "branches": 82,
         "functions": 100,
-        "lines": 94,
+        "lines": 93,
         "statements": 94
       }
     },
diff --git a/src/config.ts b/src/config.ts
new file mode 100644
index 0000000..aaa68ec
--- /dev/null
+++ b/src/config.ts
@@ -0,0 +1,10 @@
+import { IPerfumeConfig } from './types';
+
+export const config: IPerfumeConfig = {
+  // Metrics
+  resourceTiming: false,
+  // Logging
+  logPrefix: 'Perfume.js:',
+  logging: true,
+  maxMeasureTime: 15000,
+};
diff --git a/src/log.ts b/src/log.ts
new file mode 100644
index 0000000..be391ea
--- /dev/null
+++ b/src/log.ts
@@ -0,0 +1,13 @@
+import { config } from './config';
+import { C } from './constants';
+
+/**
+ * Ensures console.warn exist and logging is enable for
+ * warning messages
+ */
+export const logWarn = (message: string): void => {
+  if (!config.logging) {
+    return;
+  }
+  C.warn(config.logPrefix, message);
+}
diff --git a/src/perfume.ts b/src/perfume.ts
index 84b07b5..f870809 100644
--- a/src/perfume.ts
+++ b/src/perfume.ts
@@ -1,5 +1,5 @@
 /*!
- * Perfume.js v5.0.0-rc.6 (http://zizzamia.github.io/perfume)
+ * Perfume.js v5.0.0-rc.7 (http://zizzamia.github.io/perfume)
  * Copyright 2020 Leonardo Zizzamia (https://github.com/Zizzamia/perfume.js/graphs/contributors)
  * Licensed under MIT (https://github.com/Zizzamia/perfume.js/blob/master/LICENSE)
  * @license
@@ -13,14 +13,14 @@ import {
   IPerformanceObserver,
   IPerformanceObserverEntryList,
   IPerformanceObserverType,
-  IPerfumeConfig,
   IPerfumeDataConsumption,
   IPerfumeMetrics,
   IPerfumeOptions,
   ISendTimingOptions,
 } from './types';
 
-import { C, D, W, WN, WP } from './constants';
+import { config } from './config';
+import { C, D, WN, WP } from './constants';
 import { getNavigationTiming } from './getNavigationTiming';
 import { et, getNetworkInformation, sd } from './getNetworkInformation';
 import { getIsLowEndDevice, getIsLowEndExperience } from './isLowEnd';
@@ -28,6 +28,8 @@ import {
   isPerformanceObserverSupported,
   isPerformanceSupported,
 } from './isSupported';
+import { logWarn } from './log';
+import { pushTask } from './utils';
 
 // Have private variable outside the class,
 // helps reduce the library size
@@ -36,17 +38,8 @@ let clsScore = 0;
 let lcp = 0;
 
 export default class Perfume {
-  config: IPerfumeConfig = {
-    // Metrics
-    dataConsumption: false,
-    resourceTiming: false,
-    // Logging
-    logPrefix: 'Perfume.js:',
-    logging: true,
-    maxMeasureTime: 15000,
-  };
   copyright = '© 2020 Leonardo Zizzamia';
-  version = '5.0.0-rc.6';
+  version = '5.0.0-rc.7';
   private dataConsumptionTimeout: any;
   private isHidden: boolean = false;
   private logPrefixRecording = 'Recording already';
@@ -67,7 +60,10 @@ export default class Perfume {
 
   constructor(options: IPerfumeOptions = {}) {
     // Extend default config with external options
-    this.config = Object.assign({}, this.config, options) as IPerfumeConfig;
+    config.resourceTiming = !!options.resourceTiming;
+    config.logPrefix = options.logPrefix || config.logPrefix;
+    config.logging = !!options.logging;
+    config.maxMeasureTime = options.maxMeasureTime || config.maxMeasureTime;
 
     // Exit from Perfume when basic Web Performance APIs aren't supported
     if (!isPerformanceSupported()) {
@@ -78,7 +74,7 @@ export default class Perfume {
       try {
         this.initPerformanceObserver();
       } catch (e) {
-        this.logWarn(e);
+        logWarn(e);
       }
     }
 
@@ -100,7 +96,7 @@ export default class Perfume {
       return;
     }
     if (this.metrics[markName]) {
-      this.logWarn(`${this.logPrefixRecording} started.`);
+      logWarn(`${this.logPrefixRecording} started.`);
       return;
     }
     this.metrics[markName] = true;
@@ -118,7 +114,7 @@ export default class Perfume {
       return;
     }
     if (!this.metrics[markName]) {
-      this.logWarn(`${this.logPrefixRecording} stopped.`);
+      logWarn(`${this.logPrefixRecording} stopped.`);
       return;
     }
     // End Performance Mark
@@ -127,7 +123,7 @@ export default class Perfume {
     const durationByMetric = this.performanceMeasure(markName);
     const duration2Decimal = parseFloat(durationByMetric.toFixed(2));
     delete this.metrics[markName];
-    this.pushTask(() => {
+    pushTask(() => {
       const navigatorInfo = this.getNavigatorInfo();
       navigatorInfo.isLowEndDevice = getIsLowEndDevice();
       navigatorInfo.isLowEndExperience = getIsLowEndExperience(et, sd);
@@ -316,7 +312,7 @@ export default class Perfume {
     this.initFirstInputDelay();
     this.initLargestContentfulPaint();
     // Collects KB information related to resources on the page
-    if (this.config.resourceTiming || this.config.dataConsumption) {
+    if (config.resourceTiming) {
       this.initResourceTiming();
     }
     this.initLayoutShift();
@@ -396,7 +392,7 @@ export default class Perfume {
     const navigatorInfo = this.getNavigatorInfo();
     navigatorInfo.isLowEndDevice = getIsLowEndDevice();
     navigatorInfo.isLowEndExperience = getIsLowEndExperience(et, sd);
-    this.pushTask(() => {
+    pushTask(() => {
       // Logs the metric in the internal console.log
       this.log({ measureName, data, navigatorInfo });
       // Sends the metric to an external tracking service
@@ -415,16 +411,13 @@ export default class Perfume {
   ): void {
     const duration2Decimal = parseFloat(duration.toFixed(2));
     // Stop Analytics and Logging for false negative metrics
-    if (
-      duration2Decimal > this.config.maxMeasureTime ||
-      duration2Decimal <= 0
-    ) {
+    if (duration2Decimal > config.maxMeasureTime || duration2Decimal <= 0) {
       return;
     }
     const navigatorInfo = this.getNavigatorInfo();
     navigatorInfo.isLowEndDevice = getIsLowEndDevice();
     navigatorInfo.isLowEndExperience = getIsLowEndExperience(et, sd);
-    this.pushTask(() => {
+    pushTask(() => {
       // Logs the metric in the internal console.log
       this.log({
         measureName,
@@ -447,30 +440,19 @@ export default class Perfume {
     // Don't log when page is hidden or has disabled logging
     if (
       (this.isHidden && options.measureName.indexOf('Hidden') < 0) ||
-      !this.config.logging
+      !config.logging
     ) {
       return;
     }
     const style = 'color:#ff6d00;font-size:11px;';
     C.log(
-      `%c ${this.config.logPrefix} ${options.measureName} `,
+      `%c ${config.logPrefix} ${options.measureName} `,
       style,
       options.data,
       options.navigatorInfo,
     );
   }
 
-  /**
-   * Ensures console.warn exist and logging is enable for
-   * warning messages
-   */
-  private logWarn(message: string): void {
-    if (!this.config.logging) {
-      return;
-    }
-    C.warn(this.config.logPrefix, message);
-  }
-
   /**
    * From visibilitychange listener it saves only when
    * the page gets hidden, because it's important to not
@@ -552,11 +534,10 @@ export default class Perfume {
   }): void {
     options.performanceEntries.forEach(
       (performanceEntry: IPerformanceEntry) => {
-        if (this.config.resourceTiming) {
+        if (config.resourceTiming) {
           this.logData('resourceTiming', performanceEntry);
         }
         if (
-          this.config.dataConsumption &&
           performanceEntry.decodedBodySize &&
           performanceEntry.initiatorType
         ) {
@@ -587,17 +568,6 @@ export default class Perfume {
     );
   }
 
-  /**
-   * PushTask to requestIdleCallback
-   */
-  private pushTask(cb: any): void {
-    if ('requestIdleCallback' in W) {
-      (W as any).requestIdleCallback(cb, { timeout: 3000 });
-    } else {
-      cb();
-    }
-  }
-
   /**
    * Sends the User timing measure to analyticsTracker
    */
@@ -605,14 +575,14 @@ export default class Perfume {
     // Doesn't send timing when page is hidden
     if (
       (this.isHidden && options.measureName.indexOf('Hidden') < 0) ||
-      !this.config.analyticsTracker
+      !config.analyticsTracker
     ) {
       return;
     }
     const { measureName, data, customProperties, navigatorInfo } = options;
     const eventProperties = customProperties ? customProperties : {};
     // Send metric to custom Analytics service
-    this.config.analyticsTracker({
+    config.analyticsTracker({
       metricName: measureName,
       data,
       eventProperties,
diff --git a/src/types.ts b/src/types.ts
index 3878506..0c82425 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -8,7 +8,6 @@ export interface IAnalyticsTrackerOptions {
 
 export interface IPerfumeConfig {
   // Metrics
-  dataConsumption: boolean;
   resourceTiming: boolean;
   // Analytics
   analyticsTracker?: (options: IAnalyticsTrackerOptions) => void;
@@ -20,7 +19,6 @@ export interface IPerfumeConfig {
 
 export interface IPerfumeOptions {
   // Metrics
-  dataConsumption?: boolean;
   resourceTiming?: boolean;
   // Analytics
   analyticsTracker?: (options: IAnalyticsTrackerOptions) => void;
diff --git a/src/utils.ts b/src/utils.ts
new file mode 100644
index 0000000..65c6a3e
--- /dev/null
+++ b/src/utils.ts
@@ -0,0 +1,12 @@
+import { W } from './constants';
+
+/**
+ * PushTask to requestIdleCallback
+ */
+export const pushTask = (cb: any): void => {
+  if ('requestIdleCallback' in W) {
+    (W as any).requestIdleCallback(cb, { timeout: 3000 });
+  } else {
+    cb();
+  }
+}