これは FOLIO Advent Calendar 2018 の12/3の投稿でもあります。前日は Mura-Mi さんで「社員数が100人に迫っても社員と社長との距離を保つ「CEO Radio」の取り組み」でした。

Google Apps Script (GAS) は Google Spreadsheet や Gmail などの Google アプリの拡張または、 単体でサーバーのプロビジョニングや管理なしでスクリプト実行が可能な JavaScript ライクな言語または軽量アプリケーションです。

今回はこの Google Apps Script をモダンに開発するために色々模索したため、その内容を備忘として書き残しておきます。

Google Apps Script について

Google Apps Script について従来の開発および使用方法について紹介します。

Google Apps Script の一般的な開発方法

Google Apps Script は、各 Google アプリのツールバーの ツール > スクリプトエディタ または Google Drive に Google Apps Script のアプリケーションを追加することで作成することができます。

Script Editor from ToolBar Google Drive Add App

Google Apps Script は JavaScript 1.6 をベースに、 1.7 および 1.8 の一部と ECMAScript 5 API のサブセットを提供しています。

Google Apps Script の実行

Google Apps Script はグローバルな関数をエントリーポイントとして実行されます。 無名関数はエントリーポイントとして利用できません。

// This function can use as entry point.
function myGlobalFunction() {
  console.log('Ran myGlobalFunction.');
}

// This function cannot use as entry point.
var myGlobalAnonymousFunction = function() {
  console.log('Ran myGlobalAnonymousFunction.');
};

エディタから実行

Google Apps Script はエディタから実行することができます。 デバックなど動作確認を行う際や、手動で実行したい場合などで有効です。

エディタから実行の例

イベントトリガーでの実行

Google Apps Script はいくつかのイベントトリガーを用いて実行することができます。 実行可能なトリガーは以下の 3 つです。

  • Google アプリの Simple Trigger での実行
  • 時間での実行
  • Google カレンダーの予定更新での実行
Google アプリの Simple Trigger での実行

Simple Trigger を使用するには、予約されている関数名の関数を作成することで実行可能となります。 予約されている関数は、

  • onOpen(e)
    • ユーザーが編集権限を持つスプレッドシート、ドキュメント、プレゼンテーション、またはフォームを開いた際に実行
  • onEdit(e)
    • ユーザーがスプレッドシート内の値を変更した際に実行
  • onInstall(e)
    • ユーザーがアドオンをインストールした際に実行
  • doGet(e)
    • HTTP GET リクエストを Web アプリケーションに送信した際に実行
    • 「Web アプリケーションとして導入」を実行することで、プロジェクトに一意なエンドポイントを発行
  • doPost(e)
    • HTTP POST リクエストを Web アプリケーションに送信した際に実行
    • 「Web アプリケーションとして導入」を実行することで、プロジェクトに一意なエンドポイントを発行

の全部で 4 つです。 onOpen(e)onEdit(e)onInstall(e) を使用するに当たっては、

  • Google スプレッドシート、スライド、ドキュメント、またはフォームとスクリプトのバインドが必要
  • 読み取り専用モードで開かれている場合は実行されない
  • スクリプトや API などユーザーが UI 上で操作をしないと実行されない
  • 認可が必要なスクリプトは実行されない
  • 30 秒以上かかると、スクリプト実行が kill される

などの制約があります。 詳しくは Google Developers のサイトを参照してください。

時間での実行

時間ベースでの実行は、

  • 特定の日時
  • 分毎
  • 時毎
  • 日毎
  • 週毎
  • 月毎

が選択できます。

時間トリガーの選択

Google カレンダーの予定更新での実行

Simple Trigger や時間でのトリガーと比べると少し浮いてしまっていますが、 Google カレンダーの予定更新をトリガーとしてスクリプトの実行が可能です。

Google カレンダーの予定更新トリガーの設定

ユーティリティサービス

Google Apps Script にはグローバル関数として、予め Utilities が提供されています。 主に「文字列のエンコード/デコード」、「日付の書式設定」、「JSON 操作」などの機能を提供しているユーティリティ郡です。

詳しくは Google Developers のサイトを参照してください。

サードパーティライブラリ

Google Apps Script ではサードパーティが用意したライブラリを使用することができます。 使用するにはライブラリを一意に特定するプロジェクトキーが必要になります。

公開されているライブラリの検索機能などはないため、探すのが大変なのですが、Google Apps Script List のように、有志の方が一覧を公開しています。このような情報からプロジェクトキーを特定するのが近道であると思われます。

もし他に良い検索方法などをご存知でしたらご教示ください。

マニフェストファイル

マニフェストファイルとは、 Google Apps Script を期待通りに実行するための設定ファイルです。 appsscript.json という名前のファイルであり、

  • 実行するタイムゾーン
  • oAuth の権限のスコープ
  • 依存ライブラリ
  • Web アプリケーションの権限スコープ

などが設定できます。

詳しくは Google Developers のサイトを参照してください。

Google Apps Script の便利なユースケース

Google Apps Script では Web アプリケーションとして公開すると、無料 で HTTP GET や HTTP POST のエンドポイントを実現できることがわかりました。

HTTP POST の Web アプリケーションの使用例

HTTP POST の Web アプリケーションを用意できるため、

  • Slack Bot アプリ用のサーバー

などのユースが考えられます。 フォームの代替などとして使用する場合は、しばしば CSRF の実装が必要となるため、技術的には可能ですが、少々実現のハードルが高くなるため、あまりオススメできません。

HTTP GET の Web アプリケーションの使用例

HTTP POST の Web アプリケーションを用意できるため、

  • 定期的に更新される HTML や JSON API などを RSS として配信
    • Slack の RSS App を使用して Feed 登録すると通知を得ることが可能
  • 外部リソースなどをモックアップして、新たな JSON API の提供

などのユースが考えられます。 もちろんクエリパラメーターも受け付けられるため、実現できる幅が広がると思います。

Google Apps Script のモダンな開発方法

ここまで、 Google Apps Script の基本的な開発方法などについて紹介してきました。

さて、ここからが本題です。 今までの開発方法ですと、 Web 上でのみの開発や、存在しないライブラリに関しては自分で追加するなどの対応が必要でした。

ここからは、上記の問題を解決するために色々調べて実践してみた内容を紹介します。

ローカル環境での開発

先程も述べたとおり、 Web 上のスクリプトエディタのみでの開発が主流でした。 そのため先人たちは、

などの NPM ライブラリを開発してきました。

そんな中、 2018 年 1 月に満を持して Google 社が Google Apps Script をローカル環境で開発するための clasp という CLI を NPM ライブラリとしてかつ GitHub 上 で OSS として提供を開始しました。

2018 年 12 月現在、この clasp がローカル環境での開発におけるデファクトスタンダードとなっています。

clasp について

clasp は主に Google Apps Script API を介して、

  • Google Apps Script プロジェクトの作成
  • Google Apps Script プロジェクトのコードの pull / push
  • Google Apps Script プロジェクトの Web アプリケーションの deploy / undeploy

を行うことができる CLI です。

clasp は NPM ライブラリで提供されているため、Node.js および npm コマンドを事前にインストールしておく必要があります。

$ npm install -g @google/clasp
$ clasp -h
Usage: clasp <command> [options]

clasp - The Apps Script CLI

Options:
  -v, --version
  -h, --help              output usage information

Commands:
  login [options]     Log in to script.google.com
  ()
  *                            Any other command is not supported

TypeScript のサポート

clasp1.5 から TypeScript をサポートしました。 tsc コマンドなどによる事前のビルドを必要とせず、 push 時に自動で gs 形式にトランスパイルが可能となりました。

clasp で使用される設定ファイル

clasp には、

  • .clasprc.json
  • .clasp.json
  • .claspignore

の 3 つの設定ファイルがあります。

.clasprc.json

.clasprc.json は Google Apps Script を操作するための API の認証情報が保存されています。 これは clasp login コマンドを実行すると自動で生成されるため、あまり意識する必要はありません。

.clasp.json

.clasp.json は各プロジェクトごとの設定を定義するファイルです。

{
  "scriptId": "xxxxxxxxxxxxxxx",
  "rootDir": "dist/",
  "fileExtension": "ts",
  "filePushOrder": ["file1.ts", "file2.ts"]
}

scriptId は、Google Apps Script プロジェクトの URL https://script.google.com/d/<SCRIPT_ID>/editdedit の間の文字列であり、必須項目です。 それ以外の属性は任意項目です。

詳しくは claspREADME を参照してください。

.claspignore

.claspignoreclasp push 時に、 push する内容を制限を設定するためのファイルです。 記法は .gitignore と同じです。

ローカル開発の実現調査

clasp を使用していくのがデファクトスタンダードであり、型のある TypeScript で開発を行えることが分かったため、これらを使用して開発を進めていくことにしました。

clasp と TypeScript を使用するための初期設定

modern-gas-sample という作業ディレクトリで作業を行うことを想定して、以下のコマンドで初期設定を行います。

$ mkdir modern-gas-sample
$ cd modern-gas-sample
# npm を使用せずに yarn を使用している
$ yarn init
$ yarn add @google/clasp @types/google-apps-script tslint --dev
$ yarn run tslint --init

tslint は必須では無いですが、開発効率向上のため TypeScript を書く際は導入を推奨しています。 最低限の Node.js の環境設定はこれで終わったため、次に Google Apps Script とリポジトリを連携します。

$ yarn run clasp create modern-gas-sample --rootDir ./src
$ yarn run clasp pull
$ mv src/Code.gs src/Code.ts

これにより、 src/ 以下が Google Apps Script プロジェクトと sync するようになりました。

動作確認のため、 Code.ts を、

// Code.ts
function myGlobalFunction(): void {
  const message = 'This is myGlobalFunction.';
  console.log(message);
}

上記のように編集してみて、 Google Apps Script のプロジェクトに push してみます。

$ yarn run clasp push --watch

--watch オプションを付けることで、 rootDir のディレクトリのファイルに変更があった場合、自動で push を行うことができます。

Google Apps Script のプロジェクト URL にアクセスしてみると、無事 .gs ファイルとしてトランスパイルされたコードが Google Apps Script のプロジェクトに push されていることが確認できます。

clasp と TypeScript を使用して Google Apps Script のプロジェクトに push した例

ここで注意することは、このタイミングで clasp pull をしてしまうと、ビルドされた .gs ファイルが pull されてしまうということです。 そのため、 TypeScript で記述したソースコードは必ず Git 管理を行い、 Google Apps Script プロジェクトには常に push だけを行うようにすることをお薦めします。

NPM パッケージの使用欲求

先程挙げましたユースケースの中に RSS フィードを配信するというものがありました。 このユースケースを実現するとした場合、自作で RSS フィードを生成するコードを書いても良いのですが、やはりテストされている NPM パッケージを使いたくなります。

しかし 2018 年 12 月現在、 claspES Modules をサポートしていないため、 clasp のみでは NPM パッケージを使用することができません。

もし、 Node.js の標準ライブラリや NPM パッケージを使用したい場合は、残念ながら

の力を借りなければいけません。

どうしても NPM パッケージが譲れない場合、私が現在たどり着いた答えは、

のどちらかです。

NPM パッケージ、つまり node_modules に存在するファイルが ES2015 で記述されていた場合、 webpack でバンドルした際に ES2015 のファイルが存在してしまい、 Google Apps Script で実行できません。そのため、 Babel を用いて ES5 に変換する必要があります。

ローカル開発の実践

今回は「愚直に webpack を使用して、 ts-loader および babel-loader をかます」の手法を使用して 、ニュースが配信されている JSON API を呼び出し、 RSS として配信するためのアプリケーションをローカル開発で行ってみました。

コードはこちらで公開しています。

最低限の動作は確認できていますが絶賛拡張中のため、詳しい内容に関しては、また後日ブログを書こうかと思っています。

まとめ

Google Apps Script の基本的な機能について紹介しました。 また、 現在はローカルでの開発が clasp を使用して行われるのが主流であり、一通りのことができることを紹介しました。

まだまだ clasp 含め Goole Apps Script は発展途上であるため、より便利に開発を行いたい場合は、引き続き webpack と Babel を使用し続ける必要があります。 しかし clasp は開発速度が速いため、今後の対応に目が離せません。

宣伝

FOLIO では絶賛採用強化中です。 ご興味ある方はぜひご連絡ください!

明日は KeitaMoromizato さんによる「Node.js x thrift in FOLIO」です。 引き続き FOLIO Advent Calendar 2018 をお楽しみに!