Lazuli

らずり

Modern development with Google Apps Script

Google Apps Script(以下、gas)で今っぽいモダン環境で開発出来たから書いておく。

背景

以前、会社の業務時間を管理する Slack ボットを作った(ネタ元は宮本さん)。日報が Google Drive で管理されていることもあって gas で作られている。

作って運用してみたら社内で結構好評で徐々に利用者が増えていった。
そうなるとトラブルがあったときに対応出来るような作りにしておきたい。ただ、雑なパクリだったから謎構成になってしまったし、QUnit という一昔前のテスティングフレームワークを使っていたりで、何か手を入れるとしても触りたくない気持ちしかなかった。
gas のデメリットを解消しつつ良い感じに作り直せないかなーとやってみた。

モダンな JavaScript とは

以下の要件で書けるようにした。

  • ES2015 で書く
  • mocha でテストを回す
  • ESLint で構文チェックする
  • Yarn でパッケージ管理する
  • node-google-apps-script でデプロイ

ES2015 で書く

browserify でトランスパイル出来るようにした。とはいえ、gas は独自の JavaScript 仕様なため少し工夫をする必要がある。

export default が使えない

export default な構文を普通にトランスパイルしてもエラーで動かせない。
babel-plugin-transform-es3-member-expression-literalsbabel-plugin-transform-es3-property-literals というプラグインを追加する必要がある。
これがあればトランスパイルしてもエラーが出ずに実行出来る。

.babelrc

{
  "plugins": [
    "transform-es3-member-expression-literals",
    "transform-es3-property-literals"
  ]
}
gas のクラスが無い

当然ながらローカルに gas のクラスが無いから、トランスパイル時に参照出来なくてエラーになる。
これを解決するには gasify というプラグインが必要。便利。

便利関数が微妙に使えない

確認出来た限りだと find と引数展開は使用出来なかった(gas がエラーを吐く)。もしかしたら、これもプラグインで解決出来るのかもしらんが、調べる気にならなかった。
愚直に forEach を使ってやった。forEach は使えた。

mocha でテストを回す

テストで問題になるのも gas のクラス達。
ネットを彷徨うと様々な人がテストで困っている様子。
解決案は以下のようなものが見つかる。

  • テスト用の gas スクリプトを作って、gas 上で回す
  • Sinon.JS でモックする
  • gas-local というモックツールを使ってテストする
  • 自作モックを使う
テスト用の gas スクリプトを作って、gas 上で回す

CI サービスが使えないし諦め。

Sinon.JS でモックする

さすが簡単にモックは作れる。問題は、実在するクラスがないとモックを使用出来ないということだ。
UrlFetchApp をモックするには UrlFetchApp のクラスが用意されていないといけない。
モックを作って、テスト用のクラスも作ってとだるいので諦めた。

gas-local を使う

ES2015 では動かせなかった。諦め。
たぶん頑張れば使えるんだと思う。

自作モックを使う

gas-local のやり方見てたら自分でモック作ればいいじゃんねと気付いた。
やってみた。

// setup.js
import SpreadsheetApp from './mock/SpreadsheetApp';

global.SpreadsheetApp = SpreadsheetApp;
// SpreadSheetApp.js
import Spreadsheet from './Spreadsheet';

export default {
  openById() => (Spreadsheet),
};
// SpreadSheet.js
export default {
  // do something...
};

利用する gas の API を適当に定義すれば問題なくテストに使える。
少々めんどくさいが、うまく動くので良い。

ESLint で構文チェックする

Airbnb設定を利用した。
ちょっと厳しいところもあるので適当に緩めて。

ESLint を実行したときも勿論 gas のクラスは存在しないためエラーになる。
eslint-plugin-googleappsscriptプラグインとして読み込んでいれば良しなにしてくれる。

{
  "extends": "airbnb",
  "plugins": [
    "googleappsscript"
  ],
  "env": {
    "googleappsscript/googleappsscript": true
  },
  ....
}

ESLint は CI で回すのも便利だけど、git のフックを使うともっと良い。

# .git/hooks/pre-commit
#! /bin/sh
git diff --cached --name-only --diff-filter=AM | grep '\.js$' | xargs eslint

とやっておくだけで、コミット時に自動で ESLint が走って、エラーだとコミットが中断される。

Yarn でパッケージ管理する

npm と互換があるから使うのに何も困らなかった。

yarn add package_name
yarn add package_name --dev
yarn install

これだけ使えればだいたい事は足りる。
なにより yarn.lock でバージョンを固定出来るのが良い。npm shrinkwrap は嫌な思い出しかない。

node-google-apps-script でデプロイ

node-google-apps-scriptGoogle 製のデプロイツール(といっても API を使ってアップロードするだけだが)。

gapps upload

とやるだけでアップロードしてくれる。
設定は API キー作って認証情報登録してアップロード先を指定するだけ。詳しくは README とか見て。
アプリケーションの公開までやってくれると最高なんだが、そこは手動でやらねばならん。微妙に使いづらい。
それでも自分でアップロードするのと比べると断然ラクなんだけど。