迷ったらカレー

技術系の雑多なブログ

Lighthouseのバッチ実行と結果レポートをいい感じでやるためのパッケージを作りました

nightharborというパッケージを作りました。対象ページのリストを読み込み、ひたすらlighthouseを実行して、それらの結果をレポーティングためのものです。名前はlighthouse(灯台)が稼働してそうなとこ、という安直な感じでつけました。

github.com

lighthouseって???

本題の前に、lighthouseについて軽くお話しします。ご存知の方も多いかとおもいますが、Googleが提供しているサイトパフォーマンス計測ツールです。サイトパフォーマンスをスコア化して表示してくれるだけでなく、読み込むまでにどれくらい時間がかかったのか、ページ速度を遅くしている要因が何なのか、どうすれば改善できるのか、などいろいろ丁寧に教えてくれるステキツールです。Chromeの開発者ツールにある Audits から使うことができます。ブラウザからの使い方は こちらなどをご覧いただければと思います。

本題

このlighthouseというツールですが、node.js上でも動きます。 今回作ったパッケージは、node.js上でlighthouseをバッチ実行するためのものです。

それなりの規模のWebサイトになってくると、パフォーマンスを調べたいページについて、手動でぽちぽちlighthouseを実行するのは結構な手間です。 加えて、「定期的にモニタリングしたい」とか「競合の計測もしたい」なんて要望もでてきたりしたら大変です。手動実行なんてしたくないですし、結果のファイルをどこかに手動アップロードしまくるなんてのも絶対にやりたくありません。

なので、やりたいこととしては、ざっくり以下のような感じです。

  1. 好きな場所から対象ページリストを読み込む
  2. 順番にlighthouseを実行する
  3. 好きな場所に好きな形式で出力する

1, 3を自由にカスタマイズできて、2は設定(lighthouse, puppeteer)が渡せるように作りました。図にするとこんな感じです。

f:id:yoshiyuki-kato:20180917003247p:plain

基本的な使い方

基本的な使い方については、サンプルプロジェクトをcloneして実行していただくのが一番手っ取り早いかと思います。

$ git clone https://github.com/YoshiyukiKato/nightharbor-example.git
$ cd nightharbor-example
$ npm install
$ npm start
https://google.com
    first-contentful-paint: 0.98
    first-meaningful-paint: 0.98
    speed-index: 0.99
    screenshot-thumbnails: null
    final-screenshot: null
    estimated-input-latency: 0.82
    time-to-first-byte: 1
    first-cpu-idle: 0.87
    interactive: 0.88
    user-timings: null
    critical-request-chains: null
    redirects: 1
    mainthread-work-breakdown: 0.75
    bootup-time: 0.97
    uses-rel-preload: 1
    uses-rel-preconnect: 0.74
    font-display: 1
    network-requests: null
    metrics: null
    uses-long-cache-ttl: 1
    total-byte-weight: 1
    offscreen-images: 1
    render-blocking-resources: 1
    unminified-css: 1
    unminified-javascript: 1
    unused-css-rules: 1
    uses-webp-images: 1
    uses-optimized-images: 1
    uses-text-compression: 1
    uses-responsive-images: 1
    efficient-animated-content: 1
    dom-size: 1

こんな感じの実行結果がコンソールに出力されたでしょうか。 nhb.config.jsには、このプロジェクトの全ての設定が書いてあります。

const {SimpleLoader} = require("nightharbor/loader");
const {SimpleReporter} = require("nightharbor/reporter");

module.exports = {
  loaders: [
    new SimpleLoader([
      { url: "https://google.com" }
    ])
  ],
  reporters: [
    new SimpleReporter()
  ],
  
  //following params are optional
  //default params are shown as comment 
  /*
  chromeNum: 1,
  puppeteerConfig: {
    headless: true
  },
  lighthouseConfig: {
    extends: 'lighthouse:default',
    settings: {
      onlyCategories: ['performance'],
    }
  }
  */
}

nightharborでは、CLIからの実行とプログラム上での実行の二つを用意していますが、どちらもこのconfigを渡すだけでOKです。 CLIの場合はconfigファイルへのpathを

$ npm i -g nightharbor
$ nhb --config ./path/to/config.js

プログラムからの場合は直接configオブジェクトをexecメソッドに渡します。

import nhb from "nightharbor";
import config from "./path/to/config";

nhb.exec(config)
  .then(() => console.log("done"));
  .catch(console.error);

ここからは、nhb.config.jsの内容について説明をしていきます。

loaders

loadersは、lighthouse実行対象リストを読み込むLoaderの配列です。SimpleLoaderは、組み込みのもっとも簡単なLoaderで、生の配列でlighthouseの実行対象を指定するためのものです。Loaderが扱う実行対象の情報には、urlというパラメータが含まれている必要があります。

{ "url": "https://google.com" }

いまのところ、組み込みのSimpleLoaderに加えて、僕自身が使うために作ったLoaderは以下の2つです。

loadersには、Loaderのインタフェースを実装したクラスのインスタンスであれば、含めることができます。以下のような感じで独自のLoaderを作ることもできます。

class CustomLoader {
  /**
   * @return {Promise<{ url: string, [key: string]: any }[]>}
   */
  load(){
    //some asynchronous fetch tasks such as read file and api request.
    return Promise.resolve([
      { url: "https://google.com" }
    ]);
  }
}

load メソッドがあればいいのでサクッと作れます。 なお、ここで読み込む対象ページの情報は、最終的にlighthouseの実行結果と結合されReporterに渡されます。なので、url以外にもレポートに含めたい情報を含めておくと便利かもしれません。

reporters

reportersは、lighthouseの実行結果(audits)を任意の形で出力するReporterの配列です。SimpleReporterは、組み込みのもっとも簡単なReporterで、コンソールにlighthouseの結果を出力します。

いまのところ、組み込みのSimpleReporterに加えて、僕自身が使うために作ったReporterは以下の三つです。

reportersには、Reporterのインタフェースを実装したクラスのインスタンスであれば、含めることができます。独自のReporterを作ることもできます。

class CustomReporter{
  /**
   * will be called when a lighthouse execution completed
   * @param {any} result
   * @return {void}
   */
  write(result){
    //do something
  }

  /**
   * will be called after all executions
   * @return {Promise}
   */
  close(){
    //do something
  }
}

Reporterには、一回のlighthouse実行後に結果が渡されるwriteメソッドと、全ての対象ページへのlighthouse実行が終了した後に呼ばれるcloseメソッドを実装します。なお、現状writeメソッドに渡ってくる結果情報は、データサイズを抑えるため、各audit(例えば、speed-indexなど)ごとscoreのみにしています(auditのインタフェースはこちらを見てください)。detailsなど他の情報も取りたいという要望ありましたらIssueください

chromeNum

puppeteerで起動するchromeの数です。対象ページが多い場合、一つのchromeで処理するのでは流石に時間がかかるので、何個も立ち上げて同時に処理を走らせることができます。ただし、数字を大きく設定する場合、マシンスペックを十分に確保した上で行う必要があります。

puppeteerConfig

puppeteerの設定です。詳しくはこちらをご覧ください。

lighthouseConfig

lighthouseの設定です。詳しくはこちらをご覧ください。

実際の使い方の例

僕自身は、S3から対象リストのCSVファイルを読み込ませるようにして、CloudWatch + AWS Batchで定期実行し、結果をS3とBigQueryに出力しています。 nightharborのアプリケーションはdocker imageにして、AWS ECRに上げています。

f:id:yoshiyuki-kato:20180917025457p:plain

アプリケーションのimageはビルド後のサイズが気になるのでnodeのalpineベースで作ったのですが、lighthouseの公式docに従って進めても動かないという罠があって結構大変でした。AWS上での設定や実際に定期計測してみた話はまた別途記事にまとめようかと思いますが、とりあえずalpineベースのサンプルプロジェクトを作ったので、よかったらご活用ください。

github.com

まとめ

lighthouseをバッチ実行していい感じでレポーティングするためのパッケージ、nightharborをご紹介しました。 何かのお役に立てば幸いです。IssueもPRもお待ちしております(日本語でも英語でも大丈夫です)。