UNPKG

lighthouse

Version:

Automated auditing, performance metrics, and best practices for the web.

137 lines (113 loc) 4.1 kB
/** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Gathers a list of detected JS libraries and their versions. */ /* 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;