Node.js
Heroku
GoogleSpreadSheet
Lighthouse

heroku と Google スプレッドシートを使って、lighthouse の計測を毎日行う方法

lighthouse の計測を毎日定時実行したくて、NOW を使ってやってみたところ、成功したり失敗したりとうまくいかなかったので、heroku と Google スプレッドシートを使ってやってみました

概要

全体の流れとしては以下のような形になります

  1. Google スプレッドシートでセットされた日時トリガーから、heroku へ計測用の API をキック
  2. Google スプレッドシートで取得用の関数を実行できるよう 5 分後にトリガーをセット
  3. heroku 内で lighthouse を実行し、結果をjson に吐き出す
  4. Google スプレッドシートで取得用の関数が実行され、heroku からjson を取得してスプレッドシートに書き出す

heroku

heroku では/tmpフォルダは一時ファイルを格納できるらしいということで、計測を行った結果を/tmpフォルダに json ファイルを格納し、アクセスすることで json を取得できるようにしました

実際のコードは以下に置いてます
takutakuma/lighthouse_measurement

heroku メモ

lighthouse を使うため、buildpacks にheroku-buildpack-google-chromeを追加しています

heroku create
heroku buildpacks:set https://github.com/heroku/heroku-buildpack-nodejs\#v83
heroku buildpacks:add --index 2 https://github.com/heroku/heroku-buildpack-google-chrome.git
git push heroku master

lighthouse 実行用関数

lighthouse の実行用関数は以下のような形で作成
lighthouse のスコアを CLI だけで確認するを参考にして、追加で項目を取得しています

const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');

const perfConfig = require('lighthouse/lighthouse-core/config/perf.json');

function launchChromeAndRunLighthouse(url, opts, config = null) {
  return chromeLauncher
    .launch({ chromeFlags: opts.chromeFlags })
    .then(chrome => {
      opts.port = chrome.port;
      return lighthouse(url, opts, config).then(results => {
        // The gathered artifacts are typically removed as they can be quite large (~50MB+)
        delete results.artifacts;
        return chrome.kill().then(() => {
          const scoreMap = Object.entries(results.audits).reduce(
            (acc, [key, a]) => {
              if (typeof a.score === 'number') {
                return Object.assign({}, acc, { [key]: a.score });
              }
              return acc;
            },
            {}
          );

          const scoreCategories = Object.entries(
            results.reportCategories
          ).reduce((acc, [key, a]) => {
            return Object.assign({}, acc, { [a.name]: a.score });
            return acc;
          }, {});

          return Object.assign(scoreCategories, scoreMap);
        });
      });
    });
}

const opts = {
  port: 0,
  autoSelectChrome: true, // False to manually select which Chrome install.
  chromeFlags: ['--headless', '--disable-gpu', '--no-sandbox']
};

// Usage:
module.exports = url => {
  return launchChromeAndRunLighthouse('https://' + url, opts);
};

実行結果

単体で実行すると結果がこんな感じで取得できます

{
  Performance: 30.294117647058822,
  "screenshot-thumbnails": 100,
  "unminified-css": 90,
  "Progressive Web App": 45.45454545454545,
  "consistently-interactive": 22,
  Accessibility: 55.913978494623656,
  redirects: 100,
  "uses-rel-preload": 0,
  "total-byte-weight": 89,
  "dom-size": 78,
  "Best Practices": 62.5,
  SEO: 90,
  "network-requests": 100,
  "first-meaningful-paint": 44,
  "estimated-input-latency": 19,
  "first-interactive": 25,
  "speed-index-metric": 41,
  "uses-long-cache-ttl": 43,
  "uses-responsive-images": 0,
  "offscreen-images": 0,
  "unminified-javascript": 90,
  "unused-css-rules": 65,
  "uses-optimized-images": 0,
  "uses-request-compression": 100,
  "uses-webp-images": 0,
  "link-blocking-first-paint": 0,
  "script-blocking-first-paint": 0
}

Google スプレッドシート

結果はこのような感じでスプレッドシートに記載してます
2018-03-20-13-26-15.png

トリガー設定用関数

function setTrigger(funcName) {
  // 5分後に実行
  ScriptApp.newTrigger(funcName)
    .timeBased()
    .after(5 * 60 * 1000)
    .create();
}

トリガー削除用関数

function delTrigger(funcName) {
  var triggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() == funcName) {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}

まとめ

herokuの無料枠を使っているので、Tokyoリージョンが使えなかったり、
Performanceの値が日によって大きく変動するので、実用に耐えれているかは微妙なところですが、
毎日計測するという当初の目標は達成できたので良かったです。