perfume.js
Advanced tools
Comparing version 0.4.0 to 0.5.0
# 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 @@ |
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
198033
1500
183