forked from GoogleChrome/lighthouse
-
Notifications
You must be signed in to change notification settings - Fork 0
/
stacks.js
137 lines (114 loc) · 4.02 KB
/
stacks.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Gathers a list of detected JS libraries and their versions.
*/
/* global window */
/* global d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests */
import fs from 'fs';
import {createRequire} from 'module';
import log from 'lighthouse-logger';
import BaseGatherer from '../base-gatherer.js';
// This is removed by esbuild (if minified), because the only usage is to resolve a module path
// but that is replaced by the inline-fs plugin, leaving `require` unused.
const require = /* #__PURE__ */ createRequire(import.meta.url);
const libDetectorSource = fs.readFileSync(
require.resolve('js-library-detector/library/libraries.js'), 'utf8');
/** @typedef {false | {version: string|number|null}} JSLibraryDetectorTestResult */
/**
* @typedef JSLibraryDetectorTest
* @property {string} id
* @property {string} icon
* @property {string} url
* @property {string|null} npm npm module name, if applicable to library.
* @property {function(Window): JSLibraryDetectorTestResult | Promise<JSLibraryDetectorTestResult>} test Returns false if library is not present, otherwise returns an object that contains the library version (set to null if the version is not detected).
*/
/**
* @typedef JSLibrary
* @property {string} id
* @property {string} name
* @property {string|number|null} version
* @property {string|null} npm
*/
/**
* Obtains a list of detected JS libraries and their versions.
*/
/* c8 ignore start */
async function detectLibraries() {
/** @type {JSLibrary[]} */
const libraries = [];
// d41d8cd98f00b204e9800998ecf8427e_ is a consistent prefix used by the detect libraries
// see https://github.com/HTTPArchive/httparchive/issues/77#issuecomment-291320900
/** @type {Record<string, JSLibraryDetectorTest>} */
// @ts-expect-error - injected libDetectorSource var
const libraryDetectorTests = d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests; // eslint-disable-line
for (const [name, lib] of Object.entries(libraryDetectorTests)) {
try {
/** @type {NodeJS.Timeout|undefined} */
let timeout;
// Some library detections are async that can never return.
// Guard ourselves from PROTOCL_TIMEOUT by limiting each detection to a max of 1s.
// See https://github.com/GoogleChrome/lighthouse/issues/11124.
const timeoutPromise = new Promise(r => timeout = setTimeout(() => r(false), 1000));
const result = await Promise.race([lib.test(window), timeoutPromise]);
if (timeout) clearTimeout(timeout);
if (result) {
libraries.push({
id: lib.id,
name: name,
version: result.version,
npm: lib.npm,
});
}
} catch (e) {}
}
return libraries;
}
/* c8 ignore stop */
/** @implements {LH.Gatherer.GathererInstance} */
class Stacks extends BaseGatherer {
constructor() {
super();
/** @type {LH.Gatherer.GathererMeta} */
this.meta = {
supportedModes: ['snapshot', 'navigation'],
};
}
/**
* @param {LH.Gatherer.Driver['executionContext']} executionContext
* @return {Promise<LH.Artifacts['Stacks']>}
*/
static async collectStacks(executionContext) {
const status = {msg: 'Collect stacks', id: 'lh:gather:collectStacks'};
log.time(status);
const jsLibraries = await executionContext.evaluate(detectLibraries, {
args: [],
deps: [libDetectorSource],
});
/** @type {LH.Artifacts['Stacks']} */
const stacks = jsLibraries.map(lib => ({
detector: 'js',
id: lib.id,
name: lib.name,
version: typeof lib.version === 'number' ? String(lib.version) : (lib.version || undefined),
npm: lib.npm || undefined,
}));
log.timeEnd(status);
return stacks;
}
/**
* @param {LH.Gatherer.Context} context
* @return {Promise<LH.Artifacts['Stacks']>}
*/
async getArtifact(context) {
try {
return await Stacks.collectStacks(context.driver.executionContext);
} catch {
return [];
}
}
}
export default Stacks;