Gatherer разработка

Gatherer — это ключевой компонент архитектуры Lighthouse, отвечающий за сбор данных о странице во время аудита. Каждый Gatherer инкапсулирует конкретную задачу: получение метаданных, измерение производительности, анализ DOM, проверку доступности и многое другое. Разработка собственного Gatherer требует понимания жизненного цикла Lighthouse и особенностей его взаимодействия с Chrome DevTools Protocol (CDP).

Основная структура Gatherer

Каждый Gatherer реализуется как класс с методом async gather(options). Метод gather вызывается движком Lighthouse и получает объект options, содержащий необходимые данные для работы:

  • driver — интерфейс для отправки команд в браузер через CDP.
  • page — объект страницы, с которым можно взаимодействовать напрямую.
  • settings — настройки аудита, включая флаги и параметры конфигурации.
  • dependencies — результаты выполнения других Gatherer, от которых зависит текущий Gatherer.

Пример минимальной структуры Gatherer:

class MyGatherer {
  async gather(options) {
    const {driver, page} = options;
    const result = await driver.sendCommand('Runtime.evaluate', {
      expression: 'document.title',
    });
    return result.result.value;
  }
}

module.exports = MyGatherer;

Взаимодействие с Chrome DevTools Protocol

Все Gatherer используют driver.sendCommand для отправки команд в браузер. Это позволяет выполнять любые действия, поддерживаемые CDP, например:

  • Network.enable и Network.getResponseBody — для анализа сетевых запросов.
  • Page.captureScreenshot — для снятия скриншотов.
  • Runtime.evaluate — для выполнения JavaScript на странице.
  • Tracing.start и Tracing.end — для сбора трассировки производительности.

Важно учитывать, что команды CDP асинхронны, поэтому методы Gatherer должны быть async и корректно обрабатывать промисы.

Хранение и возвращение данных

Gatherer должен возвращать результат в стандартизированном формате. Lighthouse использует ключевые свойства для дальнейшей обработки:

  • value — основное значение, которое вернется в аудит.
  • debugString (необязательно) — текстовое сообщение для диагностики.

Пример расширенного Gatherer с обработкой ошибок:

class TitleGatherer {
  async gather(options) {
    const {driver} = options;
    try {
      const result = await driver.sendCommand('Runtime.evaluate', {
        expression: 'document.title',
      });
      return {value: result.result.value};
    } catch (err) {
      return {value: null, debugString: `Ошибка: ${err.message}`};
    }
  }
}

module.exports = TitleGatherer;

Зависимости между Gatherer

Некоторые Gatherer требуют результатов других Gatherer для своей работы. Это реализуется через dependencies. Например, Gatherer для анализа скриптов может использовать Gatherer для сетевых запросов:

class ScriptAnalyzerGatherer {
  static get meta() {
    return {
      dependencies: ['NetworkRequestsGatherer'],
    };
  }

  async gather(options) {
    const networkRequests = options.dependencies.NetworkRequestsGatherer;
    // обработка запросов
    return {value: networkRequests.filter(r => r.resourceType === 'Script')};
  }
}

Lighthouse гарантирует, что Gatherer, от которых зависит текущий, будут выполнены раньше.

Асинхронная оптимизация и параллельное выполнение

Gatherer могут выполняться параллельно, если они не имеют зависимостей. Это позволяет ускорить аудиты. Для корректного проектирования:

  • Избегать глобальных побочных эффектов.
  • Минимизировать блокировки браузера.
  • Использовать кеширование внутри Gatherer при повторных вызовах.

Тестирование Gatherer

Для разработки и тестирования Gatherer используется LH.Gatherer.runner, а также мокирование объектов driver и page. Основные аспекты:

  • Проверка корректности возвращаемого объекта {value, debugString}.
  • Симуляция ошибок и таймаутов в CDP.
  • Тестирование зависимостей и последовательности выполнения.

Пример теста с моками:

const MyGatherer = require('./my-gatherer');

const mockDriver = {
  sendCommand: jest.fn().mockResolvedValue({result: {value: 'Test Title'}})
};

test('gather returns title', async () => {
  const gatherer = new MyGatherer();
  const result = await gatherer.gather({driver: mockDriver});
  expect(result.value).toBe('Test Title');
});

Практические рекомендации при разработке

  1. Минимизировать нагрузку на браузер — каждый Gatherer должен выполнять только конкретную задачу.
  2. Использовать стандартные ключи возвращаемого объектаvalue, debugString.
  3. Обрабатывать ошибки и таймауты — это повышает надежность аудитов.
  4. Документировать зависимости — это облегчает интеграцию с другими Gatherer.
  5. Избегать глобальных изменений страницы — Gatherer должны быть идемпотентными.

Gatherer является фундаментом архитектуры Lighthouse, обеспечивая точное и структурированное получение данных с веб-страницы. Корректная разработка Gatherer позволяет создавать гибкие и расширяемые аудиты, которые легко интегрируются в существующую систему.