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

lighthouse の計測を毎日定時実行したくて、[NOW を使ってやってみた](https://qiita.com/takutakuma/items/6f2a396cb4f70c91d3d0)ところ、成功したり失敗したりとうまくいかなかったので、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](https://github.com/takutakuma/lighthouse_measurement)

### heroku メモ

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

```zsh
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 だけで確認する](https://qiita.com/mizchi/items/105308f44cb1cc18fa8a)を参考にして、追加で項目を取得しています

```javascript
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);
};
```

#### 実行結果

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

```json
{
  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](https://qiita-image-store.s3.amazonaws.com/0/57423/5131e9e5-c8b7-58ef-276a-24737438b8cd.png)


### トリガー設定用関数

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

### トリガー削除用関数

```javascript
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`の値が日によって大きく変動するので、実用に耐えれているかは微妙なところですが、
毎日計測するという当初の目標は達成できたので良かったです。