Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

perfume.js

Package Overview
Dependencies
Maintainers
1
Versions
157
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

perfume.js - npm Package Compare versions

Comparing version 0.4.0 to 0.5.0

13

CHANGELOG.md
# Changelog
## 0.5.0 (2018-1-14)
### Features
* **config:** Pass config into constructor [#14](https://github.com/Zizzamia/perfume.js/issues/14)
### Bug Fixes
* **fcp:** Use PerformanceObserver for FCP [#18](https://github.com/Zizzamia/perfume.js/issues/18)
# Changelog
## 0.4.0 (2018-1-7)

@@ -4,0 +17,0 @@

182

dist/es/perfume.js
import ttiPolyfill from "tti-polyfill";
/**
* Creates a PerformanceObserver instance and starts observing longtask entry types.
* Note: this snippet is a temporary workaround, until browsers implement level 2
* of the Performance Observer spec and include the "buffered" flag.
* https://w3c.github.io/performance-timeline/#dom-performanceobserverinit-buffered
*/
if ("PerformanceLongTaskTiming" in window) {
var g_1 = window.__tti = {
e: [],
o: new PerformanceObserver(function (l) {
g_1.e = g_1.e.concat(l.getEntries());
}),
};
g_1.o.observe({
entryTypes: ["longtask"],
});
}
var Perfume = /** @class */ (function () {
function Perfume() {
function Perfume(options) {
if (options === void 0) { options = {}; }
this.config = {
firstContentfulPaint: false,
googleAnalytics: {
enable: false,
timingVar: "name",
},
logPrefix: "⚡️ Perfume.js:",
logging: true,
timeToInteractive: false,
};
this.firstContentfulPaintDuration = 0;
this.timeToInteractiveDuration = 0;
this.googleAnalytics = {
enable: false,
timingVar: "name",
};
this.metrics = {};
this.logPrefix = "⚡️ Perfume.js:";
this.ttiPolyfill = ttiPolyfill;
this.config = Object.assign({}, this.config, options);
if (!this.supportsPerfNow) {
global.console.warn(this.logPrefix, "Cannot be used in this browser.");
global.console.warn(this.config.logPrefix, "Cannot be used in this browser.");
}
this.firstContentfulPaint();
}

@@ -56,2 +48,14 @@ Object.defineProperty(Perfume.prototype, "supportsPerfNow", {

});
Object.defineProperty(Perfume.prototype, "supportsPerfObserver", {
/**
* True if the browser supports the PerformanceObserver Interface.
* Support: developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver
* @type {boolean}
*/
get: function () {
return "PerformanceLongTaskTiming" in window;
},
enumerable: true,
configurable: true
});
/**

@@ -66,6 +70,6 @@ * Start performance measurement

if (!this.supportsPerfMark) {
global.console.warn(this.logPrefix, "Timeline won't be marked for \"" + metricName + "\".");
global.console.warn(this.config.logPrefix, "Timeline won't be marked for \"" + metricName + "\".");
}
if (this.metrics[metricName]) {
global.console.warn(this.logPrefix, "Recording already started.");
global.console.warn(this.config.logPrefix, "Recording already started.");
return;

@@ -82,6 +86,4 @@ }

* @param {string} metricName
* @param {boolean} log
*/
Perfume.prototype.end = function (metricName, log) {
if (log === void 0) { log = false; }
Perfume.prototype.end = function (metricName) {
if (!this.checkMetricName(metricName)) {

@@ -91,3 +93,3 @@ return;

if (!this.metrics[metricName]) {
global.console.warn(this.logPrefix, "Recording already stopped.");
global.console.warn(this.config.logPrefix, "Recording already stopped.");
return;

@@ -99,3 +101,3 @@ }

var duration = this.getDurationByMetric(metricName);
if (log) {
if (this.config.logging) {
this.log(metricName, duration);

@@ -110,10 +112,8 @@ }

* @param {string} metricName
* @param {boolean} log
*/
Perfume.prototype.endPaint = function (metricName, log) {
Perfume.prototype.endPaint = function (metricName) {
var _this = this;
if (log === void 0) { log = false; }
return new Promise(function (resolve, reject) {
setTimeout(function () {
var duration = _this.end(metricName, log);
var duration = _this.end(metricName);
resolve(duration);

@@ -124,31 +124,2 @@ });

/**
* First Paint is essentially the paint after which
* the biggest above-the-fold layout change has happened.
*/
Perfume.prototype.firstContentfulPaint = function () {
var _this = this;
setTimeout(function () {
_this.firstContentfulPaintDuration = _this.getFirstPaint();
if (_this.firstContentfulPaintDuration) {
_this.log("First Contentful Paint", _this.firstContentfulPaintDuration);
}
_this.sendTiming("firstContentfulPaint", _this.firstContentfulPaintDuration);
});
};
/**
* The polyfill exposes a getFirstConsistentlyInteractive() method,
* which returns a promise that resolves with the TTI value.
*
* The getFirstConsistentlyInteractive() method accepts an optional
* startTime configuration option, allowing you to specify a lower bound
* for which you know your app cannot be interactive before.
* By default the polyfill uses DOMContentLoaded as the start time,
* but it's often more accurate to use something like the moment your hero elements
* are visible or the point when you know all your event listeners have been added.
*/
Perfume.prototype.timeToInteractive = function () {
ttiPolyfill.getFirstConsistentlyInteractive()
.then(this.timeToInteractiveResolve.bind(this));
};
/**
* Coloring Text in Browser Console

@@ -160,3 +131,3 @@ * @param {string} metricName

if (!metricName || !duration) {
global.console.warn(this.logPrefix, "Please provide a metric name and the duration value");
global.console.warn(this.config.logPrefix, "Please provide a metric name and the duration value");
return;

@@ -166,3 +137,3 @@ }

var style = "color: #ff6d00;font-size:12px;";
var text = "%c " + this.logPrefix + " " + metricName + " " + durationMs + " ms";
var text = "%c " + this.config.logPrefix + " " + metricName + " " + durationMs + " ms";
global.console.log(text, style);

@@ -201,3 +172,3 @@ };

}
global.console.warn(this.logPrefix, "Please provide a metric name");
global.console.warn(this.config.logPrefix, "Please provide a metric name");
return false;

@@ -246,4 +217,36 @@ };

/**
* First Paint is essentially the paint after which
* the biggest above-the-fold layout change has happened.
*/
Perfume.prototype.firstContentfulPaint = function () {
if (this.supportsPerfObserver) {
var observer = new PerformanceObserver(this.observeFirstContentfulPaint.bind(this));
observer.observe({ entryTypes: ["paint"] });
}
else {
this.timeFirstPaint();
}
};
/**
* PerformanceObserver subscribes to performance events as they happen
* and respond to them asynchronously.
* entry.name will be either 'first-paint' or 'first-contentful-paint'
* @param {object} entryList
*/
Perfume.prototype.observeFirstContentfulPaint = function (entryList) {
for (var _i = 0, _a = entryList.getEntries(); _i < _a.length; _i++) {
var entry = _a[_i];
if (entry.name === "first-contentful-paint") {
if (this.config.firstContentfulPaint) {
this.logFCP(entry.startTime);
}
if (this.config.timeToInteractive) {
this.timeToInteractive(entry.startTime);
}
}
}
};
/**
* http://msdn.microsoft.com/ff974719
* https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart
* developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart
*/

@@ -260,2 +263,36 @@ Perfume.prototype.getFirstPaint = function () {

/**
* Uses setTimeout to retrieve FCP
*/
Perfume.prototype.timeFirstPaint = function () {
var _this = this;
setTimeout(function () {
_this.logFCP(_this.getFirstPaint());
});
};
/**
* @param {number} duration
*/
Perfume.prototype.logFCP = function (duration) {
this.firstContentfulPaintDuration = duration;
if (this.firstContentfulPaintDuration) {
this.log("First Contentful Paint", this.firstContentfulPaintDuration);
}
this.sendTiming("firstContentfulPaint", this.firstContentfulPaintDuration);
};
/**
* The polyfill exposes a getFirstConsistentlyInteractive() method,
* which returns a promise that resolves with the TTI value.
*
* The getFirstConsistentlyInteractive() method accepts an optional
* startTime configuration option, allowing you to specify a lower bound
* for which you know your app cannot be interactive before.
* By default the polyfill uses DOMContentLoaded as the start time,
* but it's often more accurate to use something like the moment your hero elements
* are visible or the point when you know all your event listeners have been added.
*/
Perfume.prototype.timeToInteractive = function (minValue) {
this.ttiPolyfill.getFirstConsistentlyInteractive({ minValue: minValue })
.then(this.timeToInteractiveResolve.bind(this));
};
/**
* @param {number} timeToInteractive

@@ -268,2 +305,5 @@ */

}
if (this.config.timeToInteractiveCb) {
this.config.timeToInteractiveCb(timeToInteractive);
}
this.sendTiming("timeToInteractive", this.timeToInteractiveDuration);

@@ -281,11 +321,11 @@ };

Perfume.prototype.sendTiming = function (metricName, duration) {
if (!this.googleAnalytics.enable) {
if (!this.config.googleAnalytics.enable) {
return;
}
if (!window.ga) {
global.console.warn(this.logPrefix, "Google Analytics has not been loaded");
global.console.warn(this.config.logPrefix, "Google Analytics has not been loaded");
return;
}
var durationInteger = Math.round(duration);
window.ga("send", "timing", metricName, this.googleAnalytics.timingVar, durationInteger);
window.ga("send", "timing", metricName, this.config.googleAnalytics.timingVar, durationInteger);
};

@@ -292,0 +332,0 @@ return Perfume;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tti_polyfill_1 = require("tti-polyfill");
/**
* Creates a PerformanceObserver instance and starts observing longtask entry types.
* Note: this snippet is a temporary workaround, until browsers implement level 2
* of the Performance Observer spec and include the "buffered" flag.
* https://w3c.github.io/performance-timeline/#dom-performanceobserverinit-buffered
*/
if ("PerformanceLongTaskTiming" in window) {
var g_1 = window.__tti = {
e: [],
o: new PerformanceObserver(function (l) {
g_1.e = g_1.e.concat(l.getEntries());
}),
};
g_1.o.observe({
entryTypes: ["longtask"],
});
}
var Perfume = /** @class */ (function () {
function Perfume() {
function Perfume(options) {
if (options === void 0) { options = {}; }
this.config = {
firstContentfulPaint: false,
googleAnalytics: {
enable: false,
timingVar: "name",
},
logPrefix: "⚡️ Perfume.js:",
logging: true,
timeToInteractive: false,
};
this.firstContentfulPaintDuration = 0;
this.timeToInteractiveDuration = 0;
this.googleAnalytics = {
enable: false,
timingVar: "name",
};
this.metrics = {};
this.logPrefix = "⚡️ Perfume.js:";
this.ttiPolyfill = tti_polyfill_1.default;
this.config = Object.assign({}, this.config, options);
if (!this.supportsPerfNow) {
global.console.warn(this.logPrefix, "Cannot be used in this browser.");
global.console.warn(this.config.logPrefix, "Cannot be used in this browser.");
}
this.firstContentfulPaint();
}

@@ -58,2 +50,14 @@ Object.defineProperty(Perfume.prototype, "supportsPerfNow", {

});
Object.defineProperty(Perfume.prototype, "supportsPerfObserver", {
/**
* True if the browser supports the PerformanceObserver Interface.
* Support: developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver
* @type {boolean}
*/
get: function () {
return "PerformanceLongTaskTiming" in window;
},
enumerable: true,
configurable: true
});
/**

@@ -68,6 +72,6 @@ * Start performance measurement

if (!this.supportsPerfMark) {
global.console.warn(this.logPrefix, "Timeline won't be marked for \"" + metricName + "\".");
global.console.warn(this.config.logPrefix, "Timeline won't be marked for \"" + metricName + "\".");
}
if (this.metrics[metricName]) {
global.console.warn(this.logPrefix, "Recording already started.");
global.console.warn(this.config.logPrefix, "Recording already started.");
return;

@@ -84,6 +88,4 @@ }

* @param {string} metricName
* @param {boolean} log
*/
Perfume.prototype.end = function (metricName, log) {
if (log === void 0) { log = false; }
Perfume.prototype.end = function (metricName) {
if (!this.checkMetricName(metricName)) {

@@ -93,3 +95,3 @@ return;

if (!this.metrics[metricName]) {
global.console.warn(this.logPrefix, "Recording already stopped.");
global.console.warn(this.config.logPrefix, "Recording already stopped.");
return;

@@ -101,3 +103,3 @@ }

var duration = this.getDurationByMetric(metricName);
if (log) {
if (this.config.logging) {
this.log(metricName, duration);

@@ -112,10 +114,8 @@ }

* @param {string} metricName
* @param {boolean} log
*/
Perfume.prototype.endPaint = function (metricName, log) {
Perfume.prototype.endPaint = function (metricName) {
var _this = this;
if (log === void 0) { log = false; }
return new Promise(function (resolve, reject) {
setTimeout(function () {
var duration = _this.end(metricName, log);
var duration = _this.end(metricName);
resolve(duration);

@@ -126,31 +126,2 @@ });

/**
* First Paint is essentially the paint after which
* the biggest above-the-fold layout change has happened.
*/
Perfume.prototype.firstContentfulPaint = function () {
var _this = this;
setTimeout(function () {
_this.firstContentfulPaintDuration = _this.getFirstPaint();
if (_this.firstContentfulPaintDuration) {
_this.log("First Contentful Paint", _this.firstContentfulPaintDuration);
}
_this.sendTiming("firstContentfulPaint", _this.firstContentfulPaintDuration);
});
};
/**
* The polyfill exposes a getFirstConsistentlyInteractive() method,
* which returns a promise that resolves with the TTI value.
*
* The getFirstConsistentlyInteractive() method accepts an optional
* startTime configuration option, allowing you to specify a lower bound
* for which you know your app cannot be interactive before.
* By default the polyfill uses DOMContentLoaded as the start time,
* but it's often more accurate to use something like the moment your hero elements
* are visible or the point when you know all your event listeners have been added.
*/
Perfume.prototype.timeToInteractive = function () {
tti_polyfill_1.default.getFirstConsistentlyInteractive()
.then(this.timeToInteractiveResolve.bind(this));
};
/**
* Coloring Text in Browser Console

@@ -162,3 +133,3 @@ * @param {string} metricName

if (!metricName || !duration) {
global.console.warn(this.logPrefix, "Please provide a metric name and the duration value");
global.console.warn(this.config.logPrefix, "Please provide a metric name and the duration value");
return;

@@ -168,3 +139,3 @@ }

var style = "color: #ff6d00;font-size:12px;";
var text = "%c " + this.logPrefix + " " + metricName + " " + durationMs + " ms";
var text = "%c " + this.config.logPrefix + " " + metricName + " " + durationMs + " ms";
global.console.log(text, style);

@@ -203,3 +174,3 @@ };

}
global.console.warn(this.logPrefix, "Please provide a metric name");
global.console.warn(this.config.logPrefix, "Please provide a metric name");
return false;

@@ -248,4 +219,36 @@ };

/**
* First Paint is essentially the paint after which
* the biggest above-the-fold layout change has happened.
*/
Perfume.prototype.firstContentfulPaint = function () {
if (this.supportsPerfObserver) {
var observer = new PerformanceObserver(this.observeFirstContentfulPaint.bind(this));
observer.observe({ entryTypes: ["paint"] });
}
else {
this.timeFirstPaint();
}
};
/**
* PerformanceObserver subscribes to performance events as they happen
* and respond to them asynchronously.
* entry.name will be either 'first-paint' or 'first-contentful-paint'
* @param {object} entryList
*/
Perfume.prototype.observeFirstContentfulPaint = function (entryList) {
for (var _i = 0, _a = entryList.getEntries(); _i < _a.length; _i++) {
var entry = _a[_i];
if (entry.name === "first-contentful-paint") {
if (this.config.firstContentfulPaint) {
this.logFCP(entry.startTime);
}
if (this.config.timeToInteractive) {
this.timeToInteractive(entry.startTime);
}
}
}
};
/**
* http://msdn.microsoft.com/ff974719
* https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart
* developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart
*/

@@ -262,2 +265,36 @@ Perfume.prototype.getFirstPaint = function () {

/**
* Uses setTimeout to retrieve FCP
*/
Perfume.prototype.timeFirstPaint = function () {
var _this = this;
setTimeout(function () {
_this.logFCP(_this.getFirstPaint());
});
};
/**
* @param {number} duration
*/
Perfume.prototype.logFCP = function (duration) {
this.firstContentfulPaintDuration = duration;
if (this.firstContentfulPaintDuration) {
this.log("First Contentful Paint", this.firstContentfulPaintDuration);
}
this.sendTiming("firstContentfulPaint", this.firstContentfulPaintDuration);
};
/**
* The polyfill exposes a getFirstConsistentlyInteractive() method,
* which returns a promise that resolves with the TTI value.
*
* The getFirstConsistentlyInteractive() method accepts an optional
* startTime configuration option, allowing you to specify a lower bound
* for which you know your app cannot be interactive before.
* By default the polyfill uses DOMContentLoaded as the start time,
* but it's often more accurate to use something like the moment your hero elements
* are visible or the point when you know all your event listeners have been added.
*/
Perfume.prototype.timeToInteractive = function (minValue) {
this.ttiPolyfill.getFirstConsistentlyInteractive({ minValue: minValue })
.then(this.timeToInteractiveResolve.bind(this));
};
/**
* @param {number} timeToInteractive

@@ -270,2 +307,5 @@ */

}
if (this.config.timeToInteractiveCb) {
this.config.timeToInteractiveCb(timeToInteractive);
}
this.sendTiming("timeToInteractive", this.timeToInteractiveDuration);

@@ -283,11 +323,11 @@ };

Perfume.prototype.sendTiming = function (metricName, duration) {
if (!this.googleAnalytics.enable) {
if (!this.config.googleAnalytics.enable) {
return;
}
if (!window.ga) {
global.console.warn(this.logPrefix, "Google Analytics has not been loaded");
global.console.warn(this.config.logPrefix, "Google Analytics has not been loaded");
return;
}
var durationInteger = Math.round(duration);
window.ga("send", "timing", metricName, this.googleAnalytics.timingVar, durationInteger);
window.ga("send", "timing", metricName, this.config.googleAnalytics.timingVar, durationInteger);
};

@@ -294,0 +334,0 @@ return Perfume;

@@ -28,32 +28,24 @@ var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

/**
* Creates a PerformanceObserver instance and starts observing longtask entry types.
* Note: this snippet is a temporary workaround, until browsers implement level 2
* of the Performance Observer spec and include the "buffered" flag.
* https://w3c.github.io/performance-timeline/#dom-performanceobserverinit-buffered
*/
if ("PerformanceLongTaskTiming" in window) {
var g_1 = window.__tti = {
e: [],
o: new PerformanceObserver(function (l) {
g_1.e = g_1.e.concat(l.getEntries());
}),
};
g_1.o.observe({
entryTypes: ["longtask"],
});
}
var Perfume = /** @class */ (function () {
function Perfume() {
function Perfume(options) {
if (options === void 0) { options = {}; }
this.config = {
firstContentfulPaint: false,
googleAnalytics: {
enable: false,
timingVar: "name",
},
logPrefix: "⚡️ Perfume.js:",
logging: true,
timeToInteractive: false,
};
this.firstContentfulPaintDuration = 0;
this.timeToInteractiveDuration = 0;
this.googleAnalytics = {
enable: false,
timingVar: "name",
};
this.metrics = {};
this.logPrefix = "⚡️ Perfume.js:";
this.ttiPolyfill = ttiPolyfill;
this.config = Object.assign({}, this.config, options);
if (!this.supportsPerfNow) {
global.console.warn(this.logPrefix, "Cannot be used in this browser.");
global.console.warn(this.config.logPrefix, "Cannot be used in this browser.");
}
this.firstContentfulPaint();
}

@@ -83,2 +75,14 @@ Object.defineProperty(Perfume.prototype, "supportsPerfNow", {

});
Object.defineProperty(Perfume.prototype, "supportsPerfObserver", {
/**
* True if the browser supports the PerformanceObserver Interface.
* Support: developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver
* @type {boolean}
*/
get: function () {
return "PerformanceLongTaskTiming" in window;
},
enumerable: true,
configurable: true
});
/**

@@ -93,6 +97,6 @@ * Start performance measurement

if (!this.supportsPerfMark) {
global.console.warn(this.logPrefix, "Timeline won't be marked for \"" + metricName + "\".");
global.console.warn(this.config.logPrefix, "Timeline won't be marked for \"" + metricName + "\".");
}
if (this.metrics[metricName]) {
global.console.warn(this.logPrefix, "Recording already started.");
global.console.warn(this.config.logPrefix, "Recording already started.");
return;

@@ -109,6 +113,4 @@ }

* @param {string} metricName
* @param {boolean} log
*/
Perfume.prototype.end = function (metricName, log) {
if (log === void 0) { log = false; }
Perfume.prototype.end = function (metricName) {
if (!this.checkMetricName(metricName)) {

@@ -118,3 +120,3 @@ return;

if (!this.metrics[metricName]) {
global.console.warn(this.logPrefix, "Recording already stopped.");
global.console.warn(this.config.logPrefix, "Recording already stopped.");
return;

@@ -126,3 +128,3 @@ }

var duration = this.getDurationByMetric(metricName);
if (log) {
if (this.config.logging) {
this.log(metricName, duration);

@@ -137,10 +139,8 @@ }

* @param {string} metricName
* @param {boolean} log
*/
Perfume.prototype.endPaint = function (metricName, log) {
Perfume.prototype.endPaint = function (metricName) {
var _this = this;
if (log === void 0) { log = false; }
return new Promise(function (resolve, reject) {
setTimeout(function () {
var duration = _this.end(metricName, log);
var duration = _this.end(metricName);
resolve(duration);

@@ -151,31 +151,2 @@ });

/**
* First Paint is essentially the paint after which
* the biggest above-the-fold layout change has happened.
*/
Perfume.prototype.firstContentfulPaint = function () {
var _this = this;
setTimeout(function () {
_this.firstContentfulPaintDuration = _this.getFirstPaint();
if (_this.firstContentfulPaintDuration) {
_this.log("First Contentful Paint", _this.firstContentfulPaintDuration);
}
_this.sendTiming("firstContentfulPaint", _this.firstContentfulPaintDuration);
});
};
/**
* The polyfill exposes a getFirstConsistentlyInteractive() method,
* which returns a promise that resolves with the TTI value.
*
* The getFirstConsistentlyInteractive() method accepts an optional
* startTime configuration option, allowing you to specify a lower bound
* for which you know your app cannot be interactive before.
* By default the polyfill uses DOMContentLoaded as the start time,
* but it's often more accurate to use something like the moment your hero elements
* are visible or the point when you know all your event listeners have been added.
*/
Perfume.prototype.timeToInteractive = function () {
ttiPolyfill.getFirstConsistentlyInteractive()
.then(this.timeToInteractiveResolve.bind(this));
};
/**
* Coloring Text in Browser Console

@@ -187,3 +158,3 @@ * @param {string} metricName

if (!metricName || !duration) {
global.console.warn(this.logPrefix, "Please provide a metric name and the duration value");
global.console.warn(this.config.logPrefix, "Please provide a metric name and the duration value");
return;

@@ -193,3 +164,3 @@ }

var style = "color: #ff6d00;font-size:12px;";
var text = "%c " + this.logPrefix + " " + metricName + " " + durationMs + " ms";
var text = "%c " + this.config.logPrefix + " " + metricName + " " + durationMs + " ms";
global.console.log(text, style);

@@ -228,3 +199,3 @@ };

}
global.console.warn(this.logPrefix, "Please provide a metric name");
global.console.warn(this.config.logPrefix, "Please provide a metric name");
return false;

@@ -273,4 +244,36 @@ };

/**
* First Paint is essentially the paint after which
* the biggest above-the-fold layout change has happened.
*/
Perfume.prototype.firstContentfulPaint = function () {
if (this.supportsPerfObserver) {
var observer = new PerformanceObserver(this.observeFirstContentfulPaint.bind(this));
observer.observe({ entryTypes: ["paint"] });
}
else {
this.timeFirstPaint();
}
};
/**
* PerformanceObserver subscribes to performance events as they happen
* and respond to them asynchronously.
* entry.name will be either 'first-paint' or 'first-contentful-paint'
* @param {object} entryList
*/
Perfume.prototype.observeFirstContentfulPaint = function (entryList) {
for (var _i = 0, _a = entryList.getEntries(); _i < _a.length; _i++) {
var entry = _a[_i];
if (entry.name === "first-contentful-paint") {
if (this.config.firstContentfulPaint) {
this.logFCP(entry.startTime);
}
if (this.config.timeToInteractive) {
this.timeToInteractive(entry.startTime);
}
}
}
};
/**
* http://msdn.microsoft.com/ff974719
* https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart
* developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart
*/

@@ -287,2 +290,36 @@ Perfume.prototype.getFirstPaint = function () {

/**
* Uses setTimeout to retrieve FCP
*/
Perfume.prototype.timeFirstPaint = function () {
var _this = this;
setTimeout(function () {
_this.logFCP(_this.getFirstPaint());
});
};
/**
* @param {number} duration
*/
Perfume.prototype.logFCP = function (duration) {
this.firstContentfulPaintDuration = duration;
if (this.firstContentfulPaintDuration) {
this.log("First Contentful Paint", this.firstContentfulPaintDuration);
}
this.sendTiming("firstContentfulPaint", this.firstContentfulPaintDuration);
};
/**
* The polyfill exposes a getFirstConsistentlyInteractive() method,
* which returns a promise that resolves with the TTI value.
*
* The getFirstConsistentlyInteractive() method accepts an optional
* startTime configuration option, allowing you to specify a lower bound
* for which you know your app cannot be interactive before.
* By default the polyfill uses DOMContentLoaded as the start time,
* but it's often more accurate to use something like the moment your hero elements
* are visible or the point when you know all your event listeners have been added.
*/
Perfume.prototype.timeToInteractive = function (minValue) {
this.ttiPolyfill.getFirstConsistentlyInteractive({ minValue: minValue })
.then(this.timeToInteractiveResolve.bind(this));
};
/**
* @param {number} timeToInteractive

@@ -295,2 +332,5 @@ */

}
if (this.config.timeToInteractiveCb) {
this.config.timeToInteractiveCb(timeToInteractive);
}
this.sendTiming("timeToInteractive", this.timeToInteractiveDuration);

@@ -308,11 +348,11 @@ };

Perfume.prototype.sendTiming = function (metricName, duration) {
if (!this.googleAnalytics.enable) {
if (!this.config.googleAnalytics.enable) {
return;
}
if (!window.ga) {
global.console.warn(this.logPrefix, "Google Analytics has not been loaded");
global.console.warn(this.config.logPrefix, "Google Analytics has not been loaded");
return;
}
var durationInteger = Math.round(duration);
window.ga("send", "timing", metricName, this.googleAnalytics.timingVar, durationInteger);
window.ga("send", "timing", metricName, this.config.googleAnalytics.timingVar, durationInteger);
};

@@ -319,0 +359,0 @@ return Perfume;

@@ -34,32 +34,24 @@ (function (global, factory) {

/**
* Creates a PerformanceObserver instance and starts observing longtask entry types.
* Note: this snippet is a temporary workaround, until browsers implement level 2
* of the Performance Observer spec and include the "buffered" flag.
* https://w3c.github.io/performance-timeline/#dom-performanceobserverinit-buffered
*/
if ("PerformanceLongTaskTiming" in window) {
var g_1 = window.__tti = {
e: [],
o: new PerformanceObserver(function (l) {
g_1.e = g_1.e.concat(l.getEntries());
}),
};
g_1.o.observe({
entryTypes: ["longtask"],
});
}
var Perfume = /** @class */ (function () {
function Perfume() {
function Perfume(options) {
if (options === void 0) { options = {}; }
this.config = {
firstContentfulPaint: false,
googleAnalytics: {
enable: false,
timingVar: "name",
},
logPrefix: "⚡️ Perfume.js:",
logging: true,
timeToInteractive: false,
};
this.firstContentfulPaintDuration = 0;
this.timeToInteractiveDuration = 0;
this.googleAnalytics = {
enable: false,
timingVar: "name",
};
this.metrics = {};
this.logPrefix = "⚡️ Perfume.js:";
this.ttiPolyfill = ttiPolyfill;
this.config = Object.assign({}, this.config, options);
if (!this.supportsPerfNow) {
global.console.warn(this.logPrefix, "Cannot be used in this browser.");
global.console.warn(this.config.logPrefix, "Cannot be used in this browser.");
}
this.firstContentfulPaint();
}

@@ -89,2 +81,14 @@ Object.defineProperty(Perfume.prototype, "supportsPerfNow", {

});
Object.defineProperty(Perfume.prototype, "supportsPerfObserver", {
/**
* True if the browser supports the PerformanceObserver Interface.
* Support: developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver
* @type {boolean}
*/
get: function () {
return "PerformanceLongTaskTiming" in window;
},
enumerable: true,
configurable: true
});
/**

@@ -99,6 +103,6 @@ * Start performance measurement

if (!this.supportsPerfMark) {
global.console.warn(this.logPrefix, "Timeline won't be marked for \"" + metricName + "\".");
global.console.warn(this.config.logPrefix, "Timeline won't be marked for \"" + metricName + "\".");
}
if (this.metrics[metricName]) {
global.console.warn(this.logPrefix, "Recording already started.");
global.console.warn(this.config.logPrefix, "Recording already started.");
return;

@@ -115,6 +119,4 @@ }

* @param {string} metricName
* @param {boolean} log
*/
Perfume.prototype.end = function (metricName, log) {
if (log === void 0) { log = false; }
Perfume.prototype.end = function (metricName) {
if (!this.checkMetricName(metricName)) {

@@ -124,3 +126,3 @@ return;

if (!this.metrics[metricName]) {
global.console.warn(this.logPrefix, "Recording already stopped.");
global.console.warn(this.config.logPrefix, "Recording already stopped.");
return;

@@ -132,3 +134,3 @@ }

var duration = this.getDurationByMetric(metricName);
if (log) {
if (this.config.logging) {
this.log(metricName, duration);

@@ -143,10 +145,8 @@ }

* @param {string} metricName
* @param {boolean} log
*/
Perfume.prototype.endPaint = function (metricName, log) {
Perfume.prototype.endPaint = function (metricName) {
var _this = this;
if (log === void 0) { log = false; }
return new Promise(function (resolve, reject) {
setTimeout(function () {
var duration = _this.end(metricName, log);
var duration = _this.end(metricName);
resolve(duration);

@@ -157,31 +157,2 @@ });

/**
* First Paint is essentially the paint after which
* the biggest above-the-fold layout change has happened.
*/
Perfume.prototype.firstContentfulPaint = function () {
var _this = this;
setTimeout(function () {
_this.firstContentfulPaintDuration = _this.getFirstPaint();
if (_this.firstContentfulPaintDuration) {
_this.log("First Contentful Paint", _this.firstContentfulPaintDuration);
}
_this.sendTiming("firstContentfulPaint", _this.firstContentfulPaintDuration);
});
};
/**
* The polyfill exposes a getFirstConsistentlyInteractive() method,
* which returns a promise that resolves with the TTI value.
*
* The getFirstConsistentlyInteractive() method accepts an optional
* startTime configuration option, allowing you to specify a lower bound
* for which you know your app cannot be interactive before.
* By default the polyfill uses DOMContentLoaded as the start time,
* but it's often more accurate to use something like the moment your hero elements
* are visible or the point when you know all your event listeners have been added.
*/
Perfume.prototype.timeToInteractive = function () {
ttiPolyfill.getFirstConsistentlyInteractive()
.then(this.timeToInteractiveResolve.bind(this));
};
/**
* Coloring Text in Browser Console

@@ -193,3 +164,3 @@ * @param {string} metricName

if (!metricName || !duration) {
global.console.warn(this.logPrefix, "Please provide a metric name and the duration value");
global.console.warn(this.config.logPrefix, "Please provide a metric name and the duration value");
return;

@@ -199,3 +170,3 @@ }

var style = "color: #ff6d00;font-size:12px;";
var text = "%c " + this.logPrefix + " " + metricName + " " + durationMs + " ms";
var text = "%c " + this.config.logPrefix + " " + metricName + " " + durationMs + " ms";
global.console.log(text, style);

@@ -234,3 +205,3 @@ };

}
global.console.warn(this.logPrefix, "Please provide a metric name");
global.console.warn(this.config.logPrefix, "Please provide a metric name");
return false;

@@ -279,4 +250,36 @@ };

/**
* First Paint is essentially the paint after which
* the biggest above-the-fold layout change has happened.
*/
Perfume.prototype.firstContentfulPaint = function () {
if (this.supportsPerfObserver) {
var observer = new PerformanceObserver(this.observeFirstContentfulPaint.bind(this));
observer.observe({ entryTypes: ["paint"] });
}
else {
this.timeFirstPaint();
}
};
/**
* PerformanceObserver subscribes to performance events as they happen
* and respond to them asynchronously.
* entry.name will be either 'first-paint' or 'first-contentful-paint'
* @param {object} entryList
*/
Perfume.prototype.observeFirstContentfulPaint = function (entryList) {
for (var _i = 0, _a = entryList.getEntries(); _i < _a.length; _i++) {
var entry = _a[_i];
if (entry.name === "first-contentful-paint") {
if (this.config.firstContentfulPaint) {
this.logFCP(entry.startTime);
}
if (this.config.timeToInteractive) {
this.timeToInteractive(entry.startTime);
}
}
}
};
/**
* http://msdn.microsoft.com/ff974719
* https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart
* developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart
*/

@@ -293,2 +296,36 @@ Perfume.prototype.getFirstPaint = function () {

/**
* Uses setTimeout to retrieve FCP
*/
Perfume.prototype.timeFirstPaint = function () {
var _this = this;
setTimeout(function () {
_this.logFCP(_this.getFirstPaint());
});
};
/**
* @param {number} duration
*/
Perfume.prototype.logFCP = function (duration) {
this.firstContentfulPaintDuration = duration;
if (this.firstContentfulPaintDuration) {
this.log("First Contentful Paint", this.firstContentfulPaintDuration);
}
this.sendTiming("firstContentfulPaint", this.firstContentfulPaintDuration);
};
/**
* The polyfill exposes a getFirstConsistentlyInteractive() method,
* which returns a promise that resolves with the TTI value.
*
* The getFirstConsistentlyInteractive() method accepts an optional
* startTime configuration option, allowing you to specify a lower bound
* for which you know your app cannot be interactive before.
* By default the polyfill uses DOMContentLoaded as the start time,
* but it's often more accurate to use something like the moment your hero elements
* are visible or the point when you know all your event listeners have been added.
*/
Perfume.prototype.timeToInteractive = function (minValue) {
this.ttiPolyfill.getFirstConsistentlyInteractive({ minValue: minValue })
.then(this.timeToInteractiveResolve.bind(this));
};
/**
* @param {number} timeToInteractive

@@ -301,2 +338,5 @@ */

}
if (this.config.timeToInteractiveCb) {
this.config.timeToInteractiveCb(timeToInteractive);
}
this.sendTiming("timeToInteractive", this.timeToInteractiveDuration);

@@ -314,11 +354,11 @@ };

Perfume.prototype.sendTiming = function (metricName, duration) {
if (!this.googleAnalytics.enable) {
if (!this.config.googleAnalytics.enable) {
return;
}
if (!window.ga) {
global.console.warn(this.logPrefix, "Google Analytics has not been loaded");
global.console.warn(this.config.logPrefix, "Google Analytics has not been loaded");
return;
}
var durationInteger = Math.round(duration);
window.ga("send", "timing", metricName, this.googleAnalytics.timingVar, durationInteger);
window.ga("send", "timing", metricName, this.config.googleAnalytics.timingVar, durationInteger);
};

@@ -325,0 +365,0 @@ return Perfume;

declare global {
interface Window {
ga: any;
__tti: {
e: any[];
o: {
observe: any;
};
};
}
}
export default class Perfume {
config: {
firstContentfulPaint: boolean;
googleAnalytics: {
enable: boolean;
timingVar: string;
};
logPrefix: string;
logging: boolean;
timeToInteractive: boolean;
timeToInteractiveCb?: any;
};
firstContentfulPaintDuration: number;
timeToInteractiveDuration: number;
googleAnalytics: {
enable: boolean;
timingVar: string;
};
private metrics;
private logPrefix;
constructor();
private ttiPolyfill;
constructor(options?: any);
/**

@@ -34,2 +35,8 @@ * True if the browser supports the Navigation Timing API.

/**
* True if the browser supports the PerformanceObserver Interface.
* Support: developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver
* @type {boolean}
*/
readonly supportsPerfObserver: boolean;
/**
* Start performance measurement

@@ -42,29 +49,10 @@ * @param {string} metricName

* @param {string} metricName
* @param {boolean} log
*/
end(metricName: string, log?: boolean): any;
end(metricName: string): any;
/**
* End performance measurement after first paint from the beging of it
* @param {string} metricName
* @param {boolean} log
*/
endPaint(metricName: string, log?: boolean): Promise<{}>;
endPaint(metricName: string): Promise<{}>;
/**
* First Paint is essentially the paint after which
* the biggest above-the-fold layout change has happened.
*/
firstContentfulPaint(): void;
/**
* The polyfill exposes a getFirstConsistentlyInteractive() method,
* which returns a promise that resolves with the TTI value.
*
* The getFirstConsistentlyInteractive() method accepts an optional
* startTime configuration option, allowing you to specify a lower bound
* for which you know your app cannot be interactive before.
* By default the polyfill uses DOMContentLoaded as the start time,
* but it's often more accurate to use something like the moment your hero elements
* are visible or the point when you know all your event listeners have been added.
*/
timeToInteractive(): void;
/**
* Coloring Text in Browser Console

@@ -113,7 +101,39 @@ * @param {string} metricName

/**
* First Paint is essentially the paint after which
* the biggest above-the-fold layout change has happened.
*/
private firstContentfulPaint();
/**
* PerformanceObserver subscribes to performance events as they happen
* and respond to them asynchronously.
* entry.name will be either 'first-paint' or 'first-contentful-paint'
* @param {object} entryList
*/
private observeFirstContentfulPaint(entryList);
/**
* http://msdn.microsoft.com/ff974719
* https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart
* developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart
*/
private getFirstPaint();
/**
* Uses setTimeout to retrieve FCP
*/
private timeFirstPaint();
/**
* @param {number} duration
*/
private logFCP(duration);
/**
* The polyfill exposes a getFirstConsistentlyInteractive() method,
* which returns a promise that resolves with the TTI value.
*
* The getFirstConsistentlyInteractive() method accepts an optional
* startTime configuration option, allowing you to specify a lower bound
* for which you know your app cannot be interactive before.
* By default the polyfill uses DOMContentLoaded as the start time,
* but it's often more accurate to use something like the moment your hero elements
* are visible or the point when you know all your event listeners have been added.
*/
private timeToInteractive(minValue);
/**
* @param {number} timeToInteractive

@@ -120,0 +140,0 @@ */

{
"name": "perfume.js",
"version": "0.4.0",
"version": "0.5.0",
"description": "",

@@ -74,3 +74,3 @@ "keywords": [],

"branches": 92,
"functions": 94,
"functions": 92,
"lines": 96,

@@ -77,0 +77,0 @@ "statements": 96

@@ -1,2 +0,4 @@

# Perfume.js v0.4.0
<a href="http://zizzamia.github.io/perfume/"><img src="https://github.com/Zizzamia/perfume.js/blob/master/docs/src/assets/perfume-logo-v0-5-0.png" align="left" width="262" /></a>
# [Perfume.js v0.5.0](http://zizzamia.github.io/perfume/)
[![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)

@@ -6,2 +8,8 @@

<br />
<br />
<br />
<br />
<br />
## User-centric performance metrics

@@ -21,3 +29,5 @@

![Performance Metrics load timeline](https://github.com/Zizzamia/perfume.js/blob/master/docs/src/assets/perf-metrics-load-timeline.png)
## Installing

@@ -53,3 +63,3 @@

#### First Contentful Paint (FCP)
### First Contentful Paint (FCP)
This metric mark the point, immediately after navigation, when the browser renders pixels to the screen. This is important to the user because it answers the question: is it happening?

@@ -60,8 +70,24 @@

```javascript
const perfume = new Perfume();
perfume.firstContentfulPaint();
// ⚡️ Perfume.js: First Contentful Paint 601 ms
const perfume = new Perfume({
firstContentfulPaint: true
});
// ⚡️ Perfume.js: First Contentful Paint 2029.00 ms
```
#### Annotate metrics in the DevTools
### Time to Interactive (TTI)
The metric marks the point at which your application is both visually rendered and capable of reliably responding to user input. An application could be unable to respond to user input for a couple of reasons:
- The JavaScript needed to make the components on the page work hasn't yet loaded;
- There are long tasks blocking the main thread.
The **TTI** metric identifies the point at which the page's initial JavaScript is loaded and the main thread is idle (free of long tasks). See the [metric definition](https://docs.google.com/document/d/1GGiI9-7KeY3TPqS3YT271upUVimo-XiL5mwWorDUD4c/preview#) for in-depth implementation details.
```javascript
const perfume = new Perfume({
timeToInteractive: true
});
// ⚡️ Perfume.js: Time to interactive 2452.07 ms
```
### Annotate metrics in the DevTools
**Performance.mark** ([User Timing API](https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API)) is used to create an application-defined peformance entry in the browser's performance entry buffer.

@@ -72,3 +98,3 @@

fibonacci(400);
perfume.end('fibonacci', true);
perfume.end('fibonacci');
// ⚡️ Perfume.js: fibonacci 0.14 ms

@@ -78,3 +104,4 @@ ```

#### Component First Paint (CFP)
### Component First Paint (CFP)
This metric mark the point, immediately after creating a **new component**, when the browser renders pixels to the screen.

@@ -85,3 +112,3 @@

$(element).popover('toggle');
perfume.endPaint('togglePopover', true);
perfume.endPaint('togglePopover');
// ⚡️ Perfume.js: togglePopover 10.54 ms

@@ -91,6 +118,10 @@ ```

#### Custom Logging
### Custom Logging
Save the duration and print it out exactly the way you want it.
```javascript
const perfume = new Perfume({
logPrefix: "🍻 Beerjs:"
});
perfume.start('fibonacci');

@@ -100,12 +131,16 @@ fibonacci(400);

perfume.log('Custom logging', duration);
// ⚡️ Perfume.js: Custom logging 0.14 ms
// 🍻 Beerjs: Custom logging 0.14 ms
```
#### Google Analytics
### Google Analytics
To enable Perfume to send your measures to Google Analytics User timing, set the option `enable:true` and a custom [user timing variable](https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#timingVar) `timingVar:"name"`.
```javascript
const perfume = new Perfume();
perfume.googleAnalytics.enable = true;
perfume.googleAnalytics.timingVar = "userId";
const perfume = new Perfume({
googleAnalytics: {
enable: true,
timingVar: "userId"
}
});
```

@@ -115,2 +150,19 @@ ![Performance Analytics](https://github.com/Zizzamia/perfume.js/blob/master/docs/src/assets/performance-analytics.png)

### Default Options
Default options provided to Perfume.js constructor.
```javascript
const options = {
firstContentfulPaint: false,
googleAnalytics: {
enable: false,
timingVar: "name",
},
logging: true,
logPrefix: "⚡️ Perfume.js:",
timeToInteractive: false
};
```
## Develop

@@ -128,3 +180,3 @@

## Credits
## Credits and Specs
Made with ☕️ by [@zizzamia](https://twitter.com/zizzamia) and

@@ -134,4 +186,3 @@ I want to thank some friends and projects for the work they did:

- [Leveraging the Performance Metrics that Most Affect User Experience](https://developers.google.com/web/updates/2017/06/user-centric-performance-metrics) for documenting this new User-centric performance metrics;
- [Appmetrics.js](https://github.com/ebidel/appmetrics.js?files=1) for inspiring me to start writing this library and giving me some of the base ideas for the class architecture;
- [Popper.js](https://github.com/FezVrasta/popper.js/) for having inspired me to write my first library in typescript;
- [Performance Timeline Level 2](https://w3c.github.io/performance-timeline/) the definition of *PerformanceObserver* in that specification;
- [The Contributors](https://github.com/Zizzamia/perfume.js/graphs/contributors) for their much appreciated Pull Requests and bug reports;

@@ -138,0 +189,0 @@ - **you** for the star you'll give this project 😉 and for supporting me by giving my project a try 😄

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc