GASをclasp(CLIツール)+ TypeScriptでローカルで開発する
Google Apps ScriptをTypeScriptでローカルで開発する
Google Apps Script(以下、GAS)とはGoogleが開発したサーバレスな関数の実行環境です。GASはGoogleの各種サービスと連携してプログラムを実行できるため、業務やルーティンワークの自動化に最適です。
このブログでもGASを使ったハックを紹介してきました。
- Google Apps ScriptからSlackとLINEを連携するbotを作る手順を紹介します
- Gmailの新着メールをLINEに転送する by Google Apps Script
- GASで議事録のテンプレ作成と周知を自動化する
- スプレッドシートとUMLで診断チャートを作成するGoogle Apps Scriptのコードを紹介します
この記事では、claspというGoogle製のCLIツールを導入し、ローカル環境でTypeScriptを使ってGASを開発する方法をご紹介します。
まずclaspを導入し、TypeScriptを書いてGAS上でHello Worldします。次に、より実践的な使い方としてGmailをLINEに転送するコードをTypeScriptで実装します。
GAS用のCLIツールclaspを導入する
claspの導入方法は$ npm i @google/clasp -g
を実行するだけです。
$ npm i @google/clasp -g
+ @google/[email protected]
added 160 packages from 92 contributors in 13.169s
これでclasp
コマンドを実行できます。
$ clasp
clasp - The Apps Script CLI
Options:
-v, --version
-h, --help output usage information
Commands:
login [options] Log in to script.google.com
logout Log out
create [options] Create a script
...
次にGoogleアカウントを使ってログインをします。コマンドは$ clasp login
です。
$ clasp login
Warning: You seem to already be logged in *globally*. You have a ~/.clasprc.json
Logging in globally...
🔑 Authorize clasp by visiting this url:
https://accounts.google.com/o/oauth2/v2/auth?access_type=xxx
Authorization successful.
Default credentials saved to: ~/.clasprc.json (/Users/Panda/.clasprc.json).
コマンドを実行すると新しくブラウザのタブが開きます。そこで、今回GASのプロジェクトを管理したいGoogleアカウントを選択しましょう。
ログインができたらタブを閉じてもOKです。
claspでTypeScriptを扱えるようにする
claspでTypeScriptを扱えるようにします。手順はclaspのGitHubを参考にしています。
まずはHello World用のプロジェクトを作成し、GASの型情報をインストールします。
$ mkdir hello-world
$ cd hello-world
$ npm i -S @types/google-apps-script
+ @types/[email protected]
added 1 package from 3 contributors and audited 1 package in 2.471s
found 0 vulnerabilities
次に、tsconfig.json
を作成します。tsconfig.json
は以下のように定義します。
{
"compilerOptions": {
"lib": ["es2019"],
"experimentalDecorators": true
}
}
GASはJavaScriptのランタイムエンジンV8をサポートしているので、ES2019の機能を使うことができます(但し、ES modulesは除きます)。
これでTypeScriptのGASプロジェクト作成の準備ができました。
GASのプロジェクトを作成する
では、claspでプロジェクトを作成してみましょう。$ clasp create
コマンドを実行します。
$ clasp create --title "Hello World" --type standalone
\ Creating new script: Hello World...
Created new standalone script: https://script.google.com/d/XXXXXXXXXXXX/edit
Warning: files in subfolder are not accounted for unless you set a '.claspignore' file.
Cloned 1 file.
└─ appsscript.json
プロジェクトルートにappsscript.json
が作成されました。デフォルトのTimeZoneはAmerica/NewYorkなので、Asia/Tokyoに変更します。
{
"timeZone": "Asia/Tokyo",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8"
}
プロジェクトを作成できました。では、Hello Worldするコードを書いて、GASで実行してみましょう。
[基礎編]TypeScript + GASでHello Worldする
hello.ts
を作成し、以下のように記述します。
const greeter = (person: string) => {
return `Hello, ${person}!`;
}
function testGreeter() {
const user = 'Panda 🐼';
Logger.log(greeter(user));
}
GASでTypeScriptのコードを書いても動きません。しかし、claspがTypeScriptをJSにコンパイルしてくれるので、開発者はローカルでtsファイルを書くだけでいいのです。
このコードをGASにデプロイしてみましょう。コマンドは$ clasp push
です。
$ clasp push
└─ appsscript.json
└─ hello.ts
Pushed 2 files.
CLIでコマンドを実行すると、デプロイしたGASの画面を開くこともできます。$ clasp open
を実行します。
$ clasp open
Opening script: https://script.google.com/d/XXXXXXXXXXXXXXXX/edit
ブラウザが立ち上がり、タブが開きました。
TypeScriptのコードはJavaScriptのコードにコンパイルされていることが確認できます。また、TypeScriptのバージョンが3.9.5であることもコメントから見て取れますね。
// Compiled using ts2gas 3.6.2 (TypeScript 3.9.5)
var greeter = function (person) {
return "Hello, " + person + "!";
};
function testGreeter() {
var user = 'Panda 🐼';
Logger.log(greeter(user));
}
testGreeter
を実行して、ログを確認しましょう。
Hello, Panda 🐼!
のと出力されています。testGreeter
を実行できました。
[実践編]GmailをLINEに転送するGASを書く
次は、より実践的な例を紹介しましょう。
TypeScriptで記述していきます。「Gmailの新着メールをLINEに転送する by Google Apps Script」という記事で紹介したコードをTypeScript化していきます。
const LINE_NOTIFY_TOKEN = PropertiesService
.getScriptProperties()
.getProperty('LINE_NOTIFY_TOKEN')
const ENDPOINT = 'https://notify-api.line.me/api/notify'
const FROM_ADDRESS = [''].join(' OR ')
const MINUTES_INTERVAL = 5
function main() {
const notices = fetchNotices()
if (notices.length === 0) {
return
}
for (const notice of notices) {
send(notice)
}
}
function fetchNotices(): string[] {
const now = Math.floor(new Date().getTime() / 1000)
const intervalMinutesAgo = now - (60 * MINUTES_INTERVAL)
const query = `(is:unread from:(${FROM_ADDRESS}) after:${intervalMinutesAgo})`
const threads: GmailThread[] = GmailApp.search(query)
if (threads.length === 0) {
return []
}
const mails: GmailMessage[][] = GmailApp.getMessagesForThreads(threads)
const notices: string[] = []
for (const messages of mails) {
const latestMessage: GmailMessage = messages.pop()
const notice = `
--------------------------------------
件名: ${latestMessage.getSubject()}
受信日: ${latestMessage.getDate().toLocaleString()}
From: ${latestMessage.getFrom()}
--------------------------------------
${latestMessage.getPlainBody().slice(0, 350)}
`
notices.push(notice)
latestMessage.markRead()
}
return notices
}
function send(notice: string) {
if (LINE_NOTIFY_TOKEN === null) {
Logger.log('LINE_NOTIFY_TOKEN is not set.')
return
}
const options: URLFetchRequestOptions = {
'method': 'POST',
'headers': {'Authorization': `Bearer ${LINE_NOTIFY_TOKEN}`},
'payload': {'message': notice},
}
UrlFetchApp.fetch(ENDPOINT, options)
}
JSのコードと比較すると、getMessagesForThreads
の返り値が多次元配列になっていることが明確に意識できるのがとてもいいですね。
また、型定義を確認するとgetProperty
でキーが存在しない場合はnullを返すことを知れたり、fetchのoptionsの型をURLFetchRequestOptions
に設定できました。
プログラムを型で縛ることは、実行時に発生するバグを未然に防ぐことに繋がります。
なお、今回参考にした型定義を抜粋は以下の通りです。
interface Properties {
getProperty(key: string): string | null;
}
interface GmailApp {
search(query: string): GmailThread[];
getMessagesForThreads(threads: GmailThread[]): GmailMessage[][];
}
interface GmailMessage {
getSubject(): string;
getDate(): Base.Date;
getFrom(): string;
getPlainBody(): string;
markRead(): GmailMessage;
}
interface UrlFetchApp {
fetch(url: string, params: URLFetchRequestOptions): HTTPResponse;
}
interface HttpHeaders {
[key: string]: string;
}
type HttpMethod = 'get' | 'delete' | 'patch' | 'post' | 'put';
type Payload = string | { [key: string]: any } | Base.Blob;
GASをローカルでTypeScriptで開発するメリット
GASをローカルで書くことができることは大きなメリットがあります。
- GASに型注釈がつくことで、バグを生みにくくなる
- 好きなエディタを使うことができる
- 型定義にジャンプして、引数や返り値の型をチェックできる
特にIDEからショートカットキーを押すだけで型定義に飛べるので、返り値を確認するためにlogを吐き出したり公式ドキュメントを探す必要が無くなります。
このため、プログラムを組むスピードがTSがない場合と比べて爆速です。型定義はGASを書くスピードを上げるアクセルといっても過言ではないでしょう。
まとめ
コンパイラがあるプログラミング言語やTDDでもない限り、プログラムのデバッグ方法は「実行 → ログ確認 → 修正」が通常の流れです。
しかし、TypeScriptが型で縛ってくれるおかげで「実行 → ログ確認」のフィードバックループを回す回数は格段に減りました。
TypeScriptの素晴らしい開発体験をGASでも開発でも享受できるのは嬉しいことです。claspを開発し、TypeScriptに対応させてくれたGoogleのチームに感謝です。
Happy Coding 🎉