phantomas 2.2.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -69,7 +69,7 @@ module.exports = function (phantomas) {
69
69
  return f + str.substr(1);
70
70
  }
71
71
 
72
- function analyzeCss(css, context, callback) {
72
+ async function analyzeCss(css, context) {
73
73
  /**
74
74
  // force JSON output format
75
75
  options.push('--json');
@@ -94,90 +94,89 @@ module.exports = function (phantomas) {
94
94
 
95
95
  // https://www.npmjs.com/package/analyze-css#commonjs-module
96
96
  var options = {};
97
-
98
- new analyzer(css, options, function (err, results) {
99
- var offenderSrc = context || "[inline CSS]";
100
-
101
- if (err !== null) {
102
- phantomas.log("analyzeCss: sub-process failed! - %s", err);
103
-
104
- // report failed CSS parsing (issue #494(
105
- var offender = offenderSrc;
106
- if (err.message) {
107
- // Error object returned
108
- if (err.message.indexOf("Unable to parse JSON string") > 0) {
109
- offender += " (analyzeCss output error)";
110
- }
111
- } else {
112
- // Error string returned (stderror)
113
- if (
114
- err.indexOf("CSS parsing failed") > 0 ||
115
- err.indexOf("is an invalid expression") > 0
116
- ) {
117
- offender += " (" + err.trim() + ")";
118
- } else if (err.indexOf("Empty CSS was provided") > 0) {
119
- offender += " (Empty CSS was provided)";
120
- }
97
+ let results;
98
+
99
+ try {
100
+ results = await analyzer(css, options);
101
+ } catch (err) {
102
+ phantomas.log("analyzeCss: sub-process failed! - %s", err);
103
+
104
+ // report failed CSS parsing (issue #494(
105
+ var offender = offenderSrc;
106
+ if (err.message) {
107
+ // Error object returned
108
+ if (err.message.indexOf("Unable to parse JSON string") > 0) {
109
+ offender += " (analyzeCss output error)";
121
110
  }
111
+ } else {
112
+ // Error string returned (stderror)
113
+ if (
114
+ err.indexOf("CSS parsing failed") > 0 ||
115
+ err.indexOf("is an invalid expression") > 0
116
+ ) {
117
+ offender += " (" + err.trim() + ")";
118
+ } else if (err.indexOf("Empty CSS was provided") > 0) {
119
+ offender += " (Empty CSS was provided)";
120
+ }
121
+ }
122
122
 
123
- phantomas.incrMetric("cssParsingErrors");
124
- phantomas.addOffender("cssParsingErrors", offender);
123
+ phantomas.incrMetric("cssParsingErrors");
124
+ phantomas.addOffender("cssParsingErrors", offender);
125
+ return;
126
+ }
125
127
 
126
- callback();
127
- return;
128
- }
128
+ var offenderSrc = context || "[inline CSS]";
129
129
 
130
- phantomas.log(
131
- "Got results for %s from %s",
132
- offenderSrc,
133
- results.generator
134
- );
130
+ phantomas.log("Got results for %s from %s", offenderSrc, results.generator);
135
131
 
136
- var metrics = results.metrics || {},
137
- offenders = results.offenders || {};
132
+ const metrics = results.metrics,
133
+ offenders = results.offenders;
138
134
 
139
- Object.keys(metrics).forEach(function (metric) {
140
- var metricPrefixed = "css" + ucfirst(metric);
135
+ Object.keys(metrics).forEach((metric) => {
136
+ var metricPrefixed = "css" + ucfirst(metric);
141
137
 
142
- if (/Avg$/.test(metricPrefixed)) {
143
- // update the average value (see #641)
144
- phantomas.addToAvgMetric(metricPrefixed, metrics[metric]);
145
- } else {
146
- // increase metrics
147
- phantomas.incrMetric(metricPrefixed, metrics[metric]);
148
- }
138
+ if (/Avg$/.test(metricPrefixed)) {
139
+ // update the average value (see #641)
140
+ phantomas.addToAvgMetric(metricPrefixed, metrics[metric]);
141
+ } else {
142
+ // increase metrics
143
+ phantomas.incrMetric(metricPrefixed, metrics[metric]);
144
+ }
149
145
 
150
- // and add offenders
151
- if (typeof offenders[metric] !== "undefined") {
152
- offenders[metric].forEach(function (msg) {
146
+ // and add offenders
147
+ if (typeof offenders[metric] !== "undefined") {
148
+ offenders[metric].forEach((offender) => {
149
+ phantomas.addOffender(metricPrefixed, {
150
+ url: offenderSrc,
151
+ value: {
152
+ message: offender.message,
153
+ position: {
154
+ ...offender.position,
155
+ source: offender.source || "undefined",
156
+ }, // cast to object
157
+ },
158
+ });
159
+ });
160
+ }
161
+ // add more offenders (#578)
162
+ else {
163
+ switch (metricPrefixed) {
164
+ case "cssLength":
165
+ case "cssRules":
166
+ case "cssSelectors":
167
+ case "cssDeclarations":
168
+ case "cssNotMinified":
169
+ case "cssSelectorLengthAvg":
170
+ case "cssSpecificityIdAvg":
171
+ case "cssSpecificityClassAvg":
172
+ case "cssSpecificityTagAvg":
153
173
  phantomas.addOffender(metricPrefixed, {
154
174
  url: offenderSrc,
155
- value: msg,
175
+ value: metrics[metric],
156
176
  });
157
- });
177
+ break;
158
178
  }
159
- // add more offenders (#578)
160
- else {
161
- switch (metricPrefixed) {
162
- case "cssLength":
163
- case "cssRules":
164
- case "cssSelectors":
165
- case "cssDeclarations":
166
- case "cssNotMinified":
167
- case "cssSelectorLengthAvg":
168
- case "cssSpecificityIdAvg":
169
- case "cssSpecificityClassAvg":
170
- case "cssSpecificityTagAvg":
171
- phantomas.addOffender(metricPrefixed, {
172
- url: offenderSrc,
173
- value: metrics[metric],
174
- });
175
- break;
176
- }
177
- }
178
- });
179
-
180
- callback();
179
+ }
181
180
  });
182
181
  }
183
182
 
@@ -195,12 +194,12 @@ module.exports = function (phantomas) {
195
194
 
196
195
  // ok, now let's analyze the collect CSS
197
196
  phantomas.on("beforeClose", () => {
198
- var promises = [];
197
+ const promises = [];
199
198
 
200
199
  stylesheets.forEach((entry) => {
201
200
  promises.push(
202
201
  new Promise(async (resolve) => {
203
- var css = entry.inline;
202
+ let css = entry.inline;
204
203
  phantomas.log("Analyzing %s", entry.url || "inline CSS");
205
204
 
206
205
  if (entry.content) {
@@ -208,10 +207,10 @@ module.exports = function (phantomas) {
208
207
  }
209
208
 
210
209
  if (css) {
211
- analyzeCss(css, entry.url, resolve);
212
- } else {
213
- resolve();
210
+ await analyzeCss(css, entry.url);
214
211
  }
212
+
213
+ resolve();
215
214
  })
216
215
  );
217
216
  });
@@ -1,4 +1,4 @@
1
- (function (phantomas) {
1
+ (function analyzeCssScope(phantomas) {
2
2
  document.addEventListener("DOMContentLoaded", function () {
3
3
  // both DOM and CSSOM are constructed, no stylesheets are blocking JavaScript execution
4
4
  phantomas.spyEnabled(false, "Checking inline scripts");
@@ -9,15 +9,18 @@ module.exports = function (phantomas) {
9
9
  phantomas.setMetric("cachePasses"); // @desc number of cache passes @offenders
10
10
 
11
11
  phantomas.on("recv", (entry) => {
12
- var age, xCacheHeader, isHit, isMiss, isPass;
12
+ let isHit, isMiss, isPass;
13
13
 
14
14
  // parser response headers
15
15
  //
16
16
  // X-Cache:HIT, HIT
17
17
  // X-Cache:arsenic miss (0)
18
18
  // Age: 170221
19
- age = parseInt(entry.headers.Age, 10);
20
- xCacheHeader = (entry.headers["X-Cache"] || "").toLowerCase();
19
+ const age =
20
+ typeof entry.headers.Age !== "undefined"
21
+ ? parseInt(entry.headers.Age, 10)
22
+ : undefined,
23
+ xCacheHeader = (entry.headers["X-Cache"] || "").toLowerCase();
21
24
 
22
25
  if (xCacheHeader !== "") {
23
26
  isHit = xCacheHeader.indexOf("hit") > -1;
@@ -1,4 +1,4 @@
1
- (function (phantomas) {
1
+ (function documentHeightScope(phantomas) {
2
2
  document.addEventListener("DOMContentLoaded", () => {
3
3
  // @see https://github.com/HTTPArchive/httparchive/blob/master/custom_metrics/document_height.js
4
4
  var doc = document,
@@ -1,4 +1,4 @@
1
- (function (phantomas) {
1
+ (function domComplexityScope(phantomas) {
2
2
  phantomas.spyEnabled(false, "initializing domComplexity metrics");
3
3
 
4
4
  window.addEventListener("load", () => {
@@ -1,4 +1,4 @@
1
- (function (phantomas) {
1
+ (function domHiddenContentScope(phantomas) {
2
2
  phantomas.spyEnabled(false, "initializing hidden content analysis");
3
3
 
4
4
  window.addEventListener("load", () => {
@@ -1,4 +1,4 @@
1
- (function (phantomas) {
1
+ (function DOMmutationsScope(phantomas) {
2
2
  if ("MutationObserver" in window) {
3
3
  // wait for DOM ready
4
4
  document.addEventListener("readystatechange", function () {
@@ -1,4 +1,4 @@
1
- (function (phantomas) {
1
+ (function domQueriesScope(phantomas) {
2
2
  phantomas.log("domQueries: initializing page scope code");
3
3
 
4
4
  function querySpy(type, query, fnName, context, hasNoResults) {
@@ -3,7 +3,7 @@
3
3
  */
4
4
  "use strict";
5
5
 
6
- const Stats = require("../../lib/fast-stats").Stats;
6
+ const Stats = require("fast-stats").Stats;
7
7
 
8
8
  module.exports = function (phantomas) {
9
9
  var Collection = require("../../lib/collection"),
@@ -1,4 +1,5 @@
1
- (function (phantomas) {
1
+ /* istanbul ignore next */
2
+ (function eventsScope(phantomas) {
2
3
  // spy calls to EventTarget.addEventListener
3
4
  // @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener
4
5
  function eventSpy(eventType) {
@@ -1,4 +1,4 @@
1
- (function (phantomas) {
1
+ (function globalVariablesScope(phantomas) {
2
2
  // get the list of initial, built-in global variables
3
3
  var allowed = [],
4
4
  varName;
@@ -1,4 +1,4 @@
1
- (function (phantomas) {
1
+ (function jQueryScope(phantomas) {
2
2
  // read & write DOM operations (issue #436)
3
3
  function spyReadsAndWrites(jQuery) {
4
4
  var TYPE_SET = "write",
@@ -1,4 +1,4 @@
1
- (async (phantomas) => {
1
+ (async function javaScriptBottlenecksScope(phantomas) {
2
2
  // spy calls to eval only when requested (issue #467)
3
3
  var spyEval = (await phantomas.getParam("spy-eval")) === true;
4
4
 
@@ -1,4 +1,4 @@
1
- ((phantomas) => {
1
+ (function lazyLoadableImages(phantomas) {
2
2
  phantomas.spyEnabled(
3
3
  false,
4
4
  "setting up which images can be lazy-loaded analysis"
@@ -1,4 +1,4 @@
1
- (function (phantomas) {
1
+ (function localStorageScope(phantomas) {
2
2
  window.addEventListener("load", () => {
3
3
  try {
4
4
  var entries = Object.keys(window.localStorage);
@@ -5,12 +5,13 @@
5
5
 
6
6
  module.exports = function (phantomas) {
7
7
  var domains = new Map(),
8
+ mainDomain = undefined,
8
9
  beforeDomReady = true;
9
10
 
10
11
  phantomas.setMetric("mainDomainHttpProtocol"); // @desc HTTP protocol used by the main domain [string]
11
12
  phantomas.setMetric("oldHttpProtocol"); // @desc number of domains using HTTP/1.0 or 1.1
12
13
  phantomas.setMetric("mainDomainTlsProtocol"); // @desc TLS protocol used by the main domain [string]
13
- phantomas.setMetric("oldTlsProtocol"); // @desc number of domains using TLS 1.1 or 1.2
14
+ phantomas.setMetric("oldTlsProtocol"); // @desc number of domains using TLS 1.2
14
15
 
15
16
  // spy all requests
16
17
  phantomas.on("recv", (entry) => {
@@ -18,7 +19,14 @@ module.exports = function (phantomas) {
18
19
  var domain = (entry.isSSL ? "https://" : "http://") + entry.domain;
19
20
 
20
21
  if (domains.size === 0) {
22
+ mainDomain = domain;
23
+ phantomas.log("Our main domain is now %s", mainDomain);
24
+ }
25
+
26
+ if (domain == mainDomain) {
21
27
  // our first request represents the main domain
28
+ // h3 protocol is used for subsequent requests for the same domain
29
+ // we need to keep updating these metrics on each response we get
22
30
  phantomas.setMetric("mainDomainHttpProtocol", entry.httpVersion);
23
31
  phantomas.setMetric("mainDomainTlsProtocol", entry.tlsVersion);
24
32
  }
@@ -41,6 +49,11 @@ module.exports = function (phantomas) {
41
49
  } else {
42
50
  // just increment the number of requests
43
51
  domains.get(domain).requests++;
52
+
53
+ // h3 protocol is used for subsequent requests for the same domain
54
+ // initial ones are performed using h2
55
+ domains.get(domain).httpVersion = entry.httpVersion;
56
+ domains.get(domain).tlsVersion = entry.tlsVersion;
44
57
  }
45
58
  }
46
59
  });
@@ -55,7 +68,7 @@ module.exports = function (phantomas) {
55
68
  // set metrics
56
69
  phantomas.on("report", () => {
57
70
  domains.forEach(function (value, key) {
58
- // As of 2020, h2 is the latest protocol, h3 is coming
71
+ // As of 2020, h2 is the latest protocol, h3 is coming in 2021
59
72
  if (value.httpVersion.indexOf("http/1") === 0) {
60
73
  phantomas.incrMetric("oldHttpProtocol");
61
74
  phantomas.addOffender("oldHttpProtocol", {
@@ -68,9 +81,13 @@ module.exports = function (phantomas) {
68
81
  // As of 2020, TLS 1.3 is the latest protocol and it brings speed improvements over 1.2
69
82
  if (value.tlsVersion) {
70
83
  // parse version number
71
- var tlsVersion = parseFloat(value.tlsVersion.substring(4));
84
+ var tlsVersion =
85
+ value.tlsVersion === "QUIC"
86
+ ? "quic"
87
+ : parseFloat(value.tlsVersion.substring(4));
88
+ phantomas.log(`tlsVersion for ${key} domain is ${tlsVersion}`);
72
89
 
73
- if (tlsVersion < 1.3) {
90
+ if (tlsVersion !== "quic" && tlsVersion < 1.3) {
74
91
  phantomas.incrMetric("oldTlsProtocol");
75
92
  phantomas.addOffender("oldTlsProtocol", {
76
93
  domain: key,
@@ -12,7 +12,7 @@
12
12
  */
13
13
  "use strict";
14
14
 
15
- var Stats = require("../../lib/fast-stats").Stats;
15
+ var Stats = require("fast-stats").Stats;
16
16
 
17
17
  module.exports = function (phantomas) {
18
18
  var stack = {};
@@ -11,7 +11,8 @@ module.exports = function (phantomas) {
11
11
  assetsReqCounter = new Collection(),
12
12
  cookieDomains = new Collection(),
13
13
  // TODO: use 3pc database with tracking services
14
- trackingUrls = /google-analytics.com\/__utm.gif|pixel.quantserve.com\/pixel/;
14
+ trackingUrls =
15
+ /google-analytics.com\/__utm.gif|pixel.quantserve.com\/pixel/;
15
16
 
16
17
  phantomas.setMetric("assetsNotGzipped"); // @desc number of static assets that were not gzipped
17
18
  phantomas.setMetric("assetsWithQueryString"); // @desc number of static assets requested with query string (e.g. ?foo) in URL
@@ -25,6 +25,7 @@ module.exports = function (phantomas) {
25
25
  phantomas.setMetric("timeFrontend"); // @desc time to window.load compared to the total loading time [%]
26
26
 
27
27
  phantomas.on("beforeClose", async function () {
28
+ /* istanbul ignore next */
28
29
  const timing = await phantomas.evaluate(() => {
29
30
  return window.performance.timing.toJSON();
30
31
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phantomas",
3
- "version": "2.2.0",
3
+ "version": "2.5.0",
4
4
  "author": "macbre <maciej.brencz@gmail.com> (http://macbre.net)",
5
5
  "description": "Headless Chromium-based web performance metrics collector and monitoring tool",
6
6
  "main": "./lib/index.js",
@@ -24,37 +24,47 @@
24
24
  ],
25
25
  "license": "BSD-2-Clause",
26
26
  "engines": {
27
- "node": ">=12.0"
27
+ "node": ">=14.0"
28
28
  },
29
29
  "dependencies": {
30
- "analyze-css": "^0.13.0",
31
- "ansicolors": "~0.3.2",
32
- "ansistyles": "~0.1.0",
33
- "ascii-table": "0.0.9",
34
- "async": "^3.2.0",
35
- "commander": "^7.0.0",
36
- "csv-string": "^4.0.1",
30
+ "analyze-css": "^2.0.0",
31
+ "commander": "^9.0.0",
37
32
  "debug": "^4.1.1",
38
33
  "decamelize": "^5.0.0",
34
+ "fast-stats": "0.0.6",
39
35
  "js-yaml": "^4.0.0",
40
- "node-statsd": "0.1.1",
41
- "puppeteer": "^8.0.0"
36
+ "puppeteer": "^14.4.1"
42
37
  },
43
38
  "devDependencies": {
44
- "eslint": "^7.21.0",
39
+ "@jest/globals": "^28.0.0",
40
+ "eslint": "^8.0.0",
45
41
  "eslint-config-prettier": "^8.1.0",
46
42
  "eslint-plugin-node": "^11.1.0",
47
- "glob": "^7.1.6",
48
- "mockery": "^2.0.0",
49
- "prettier": "2.2.1",
50
- "vows": "^0.8.3"
43
+ "glob": "^8.0.1",
44
+ "jest": "^28.0.0",
45
+ "prettier": "2.7.1"
51
46
  },
52
47
  "scripts": {
53
- "test": "vows --spec",
54
- "unit-test": "vows test/modules/*-test.js --spec",
48
+ "test": "node --trace-warnings node_modules/.bin/jest test/ --coverage --detectOpenHandles --forceExit",
49
+ "unit-test": "jest test/results.test.js test/modules/ --coverage --detectOpenHandles --forceExit",
55
50
  "lint": "eslint --cache .",
56
51
  "metadata": "DEBUG=generate node lib/metadata/generate.js",
57
52
  "make-docs": "DEBUG=docs node lib/metadata/make_docs.js",
58
53
  "prettier": "npx prettier --write ."
54
+ },
55
+ "jest": {
56
+ "verbose": true,
57
+ "reporters": [
58
+ "default",
59
+ "github-actions"
60
+ ],
61
+ "coverageThreshold": {
62
+ "global": {
63
+ "statements": 89,
64
+ "branches": 85,
65
+ "functions": 89,
66
+ "lines": 89
67
+ }
68
+ }
59
69
  }
60
70
  }