clasp と TypeScript を用いた GAS 開発 (ローカルで GAS コーディング)

私は普段 C++、C# といった (古典的) 言語を主に使います。
ですので、ブラウザ上のエディタで GAS のコードを書く際にストレスを強く感じます。
大きなストレスポイントは次の 2 点です。

* Git で管理するのが面倒
* 好きなエディタを使えない

また、JavaScript で class を用いる場合、C++ や C# のように直感的に書くことができず、せめて TypeScript で書けたらなぁ、とも思っていました。

が、同じ思いをしている方は本家 Google にもいたようで、
Google 謹製の [clasp](https://github.com/google/clasp) というものが 2018 年に生まれ、これら全ての問題が解決されました。
以降 GAS に触れる際には常に clasp を使い書いています。

clasp の解説は、あちこちにスマートにまとめられているので、私のように稀に GAS を書く人間が clasp についてまとめた記事など役に立たないでしょうが、「自分用のメモ書き」として残したいと思います。
なお、公式のインストラクションは「[こちら](https://codelabs.developers.google.com/codelabs/clasp/#0)」にまとめらています。

# 1. node 環境の準備

node が動く環境が必要です。
何かと node を使う機会はあるので、私はまず nvm をインストールし、node のバージョンをコントロールする環境を作るのですが、このあたりはそれこそ詳しいサイトが沢山あるので割愛します。

# 2. Google アカウントの準備

GAS を使う以上、Google アカウントは必須ですが、そのアカウントで Google Apps Script API を使用できるよう、下記ダッシュボードから ON にしておきます。(これを読まれている方はとっくに ON にしていると思いますが念のため)
https://script.google.com/home/usersettings

# 3. clasp のインストール

[clasp の GitHub リポジトリ](https://github.com/google/clasp)の[インストラクション](https://github.com/google/clasp#install)通りです。

```
npm install -g @google/clasp
```

グローバル環境への影響を避けて -g (--global) オプションを使用しないインストールを案内されている方もいるようですが、clasp で他の開発プロジェクトに影響が出るように思わないですし、プロジェクトごとに毎回 clasp をインストールするのは寧ろ効率が悪いと思うので、私はインストラクション通り、-g オプションを使用します。
すなわち、以降は他の新規プロジェクトでも clasp のインストールは不要になります。


# 4. 新規プロジェクトの作成

大まかに次のような流れになります。(後述の 8. で改めてまとめてあります)

* プロジェクト用ディレクトリの作成
* `npm init -y` で npm 環境作成 (`package.json` 作成)
* "types/google-apps-script" npm パッケージの追加
* `clasp login` で使用する Google アカウントを指定
* `clasp create` でプロジェクト作成

以下に実際の手順を説明します。

## 4.1. プロジェクト用ディレクトリの作成

ローカルにプロジェクト用のディレクトリを作成するだけです。
ここでは `my_sample` というディレクトリをプロジェクト用ディレクトリとして作りました。
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1859956/a7e9742b-249b-4858-5193-7d68a3bf11f7.png)

## 4.2. npm 環境作成

GAS 書くのに npm が必要なのか？と思ってしまうところですが、[GAS の関数/型定義をまとめた npm パッケージ](https://www.npmjs.com/package/@types/google-apps-script)があり、これをエディタに読み込ませることによって、ローカルでコーディングする利便性があがるようになります。
このために `package.json` が必要になるので、次のコマンドで作成します。

```
npm init -y
```

## 4.3. "types/google-apps-script" npm パッケージの追加

先に触れた通り、[GAS の関数/型定義をまとめた npm パッケージ](https://www.npmjs.com/package/@types/google-apps-script) をプロジェクトに追加します。
VSCode などのエディタであれば、プロジェクト内の GAS スクリプトは、このパッケージに従ってキーワードハイライトしてくれます。
逆にいうと、この npm パッケージを追加しないと、GAS 依存のキーワードはワーニング表示になってしまいます。

```
npm install @types/google-apps-script
```

なお、このパッケージは GAS のバージョンアップに従い更新されていくことが期待されるので、このプロジェクト限定のインストール (`-g` / `--global` なし) が適切かと思います。

ここまでで作成されるファイル/ディレクトリは次のようになります。(一般的な node 環境そのものです)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1859956/2138d703-5ceb-da1a-7ae1-9fa5ea7fd0d8.png)


## 4.4. 使用する Google アカウントを指定

これから書こうとする GAS をどの Google アカウントで動かすかを指定します。
Google アカウントが一つしかない場合はこの作業は一度行えばやる必要はありません。

こちら ↓ だと、直前に開いていた Chrome ブラウザのアカウントを使用する前提で処理が行われます。

```
clasp login
```

一方、こちら ↓ だと、コマンド実行後に URL が表示され、それを開いた Chrome ブラウザのアカウントを使用する前提で処理が行われます。

```
clasp login --no-localhost
```

## 4.5. プロジェクト作成

プロジェクトの作成は `clasp create` で行います。

```
clasp create
```

すると、次のように対話形式でどのような GAS を作成するかを問われます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1859956/2199019d-59e1-a093-5b64-e57e2954234d.png)
項目の docs や sheets は、Google Doc に埋め込んだ GAS、Google Spreadsheet に埋め込んだ GAS… といったものを指しています。
(GAS は、当初は、といってもだいぶ昔の話ですが、Google Spreadsheet に埋め込んで利用する方法しかありませんでした)

このように埋め込んだ GAS は、一瞬で終わるタスクを処理するには便利なこともあるのですが、度重なる仕様追加で肥大化し、最終的に Google Spreadsheet を複数扱ったり、Doc や Slide とも連携させたりすることになる傾向が多いので、その状況を見越して、最初から将来のリスクヘッジのために、私は常に `standalone` 一択です。
`clasp create --type standalone` とすることもできますが、ここでは割愛します。

この段階でディレクトリにできるのは次のようなディレクトリ/ファイルになります。
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1859956/664efefc-7343-866b-1cae-a94ff230e78a.png)

`.clasp.json` は clasp の設定ファイルで、中を覗くと `scriptId`、`rootDir`といったプロパティが見えます。
`scriptId` は `clasp create` で生成された GAS ファイルの ID、`rootDir` はローカル環境でのコードの置き場所となります。

```json
{
  "scriptId": "####################################################",
  "rootDir": "C:\\Users\\########\\Develop\\my_sample"
}
```
この設定ファイルには[公式の説明](https://github.com/google/clasp#project-settings-file-claspjson)にあるように、項目を加えることができます。

次のように項目を編集、追加することにより、カスタマイズすることができます。

```json
{
  "scriptId": "####################################################",
  "rootDir": "./src",
  "fileExtension": "ts"
}
```
* **rootDir** : GAS ファイルを任意のディレクトリで扱う (この例ではプロジェクト用ディレクトリ内の `src` ディレクトリ)
* **fileExtension** : GAS ファイルの拡張子 ── `ts` を指定することにより TypeScript として扱うことができるようになります


さて、`clasp create` で設定ファイルは生成されたものの、GAS ファイルそのものはこのディレクトリには作成されていません。

コードは、`clasp login` した Google アカウントの Google ドライブのトップに作成されています。
厳密にはこれは GAS コードそのものではなく、管理するファイルなのですが。
ちなみにこのファイルのロケーションは同一 Google アカウントの Google Drive 上の好きな場所に移動させてもローカルの操作には影響ありません。
すなわち、スクリプトの ID が変わらない限り、Google Drive 上のファイルの場所はどこでも構わない、ということです。

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1859956/7b426088-2ad6-9c23-7763-864a58657069.png)

このファイルをローカルに持ってくるために `clasp pull` というコマンドがあるのですが、それをしても何も起こりません。
現状の仕様ですと、空のスクリプトに対しては `clasp pull` してもローカルに落ちてこないため、一度ブラウザの GAS エディタ上で何らかのコードを書く必要があります。
ですのでさらに次の手順を用います。


### 4.5.1. `clasp open` でブラウザ上で GAS エディタで開く

```
clasp open
```

### 4.5.2. GAS エディタで適当なコードを書いて保存

エディタの左の [Files] で "+" を押し "Script" を選ぶと初期コードが挿入されるので、それを保存します。

```
function myFunction() {
}
```

### 4.5.3. `clasp pull` してコードを持ってくる

```
clasp pull
```

これで、src ディレクトリの下に GAS のコードが作成され、ローカルで作業できるようになりました。

![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1859956/24812176-3a2d-a392-b625-25fcecd09f17.png)

なお、これは 4.3. の冒頭で書いた、.clasp.json 設定ファイルを改変した場合の結果です。この場合はプロジェクト用ディレクトリのルートにある appscript.json ファイルは不要になります。
.clasp.json を編集せずに、そのまま使った場合には、プロジェクト用ディレクトリのルートに、他のファイルと一緒に .js ファイルが作成されます。


#5. コードの編集

後は、ローカルにある GAS スクリプトを書いていくだけです。
コードを書いたら `clasp push` でコードを Google Drive にアップロードします。

```
clasp push
```

.clasp.json で fileExtension に "ts" を指定した場合には、ローカルの GAS のコードを TypeScript で書くことができます。
この場合、`clasp push` によりコンパイルされ、Google Drive には JavaScript ファイルになってアップロードされます。

#6. デバッグ上の注意点

実際の開発作業では、次のようなフローで回していくことになると思います。

(1) ローカルの GAS ファイルを編集
(2) `clasp push` でコードを Google Drive にアップロード
(3) ブラウザの GAS エディタ上でデバッグし、挙動を確認
(4) (1) に戻る

この状況だと、ブラウザの GAS エディタは開きっぱなしになりますが、`clasp push` で最新のスクリプトをアップロードしても GAS エディタには自動的に反映されないので、`clasp push` した後はブラウザをリロードする必要があります。

#7. GAS で扱うライブラリの管理

GAS で [OAuth1](https://github.com/googleworkspace/apps-script-oauth1) などのライブラリや、YouTube Data API サービスを扱うときは、appsscript.json を編集します。
appsscript.json にライブラリの追記をしないと、`clasp push` をした後、ブラウザ上の GAS エディタにマニュアルで設定したライブラリやサービスが除去されてしまいます。

```json
{
  "timeZone": "America/New_York",
  "dependencies": {
    "libraries": [
      {
        "userSymbol": "OAuth1",
        "libraryId": "1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s",
        "version": "18"
      }
    ],
    "enabledAdvancedServices": [
      {
        "userSymbol": "YouTube",
        "version": "v3",
        "serviceId": "youtube"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}
```

# 8. まとめ (新規プロジェクト作成手順チートシート)

以上を改めてまとめると、既に clasp がグローバルにインストールされた環境での新規プロジェクトの作成手順は下記のようになります。

(1) ローカルにてプロジェクト用ディレクトリの作成
(2) `npm init -y`
(3) `npm install @types/google-apps-script`
(4) `clasp login`
(5) `clasp create`
(6) [任意] .clasp.json の編集 (rootDir と、TypeScript を使う場合は "fileExtension":"ts")
(7) `clasp open` の後、ブラウザ上の GAS エディタでスクリプトの追加 (ライブラリ類の追加もここでやるとラク)
(8) `clasp pull` してローカルに持ってくる
(9) コーディング
(10) `clasp push`

(注意 1) (6) で rootDir を変更した場合は、元々あったトップの appsscript.json は使われないので消して良い (変更した rootDir の appsscript.json が使用されるので)
(注意 2) ライブラリの追加 / 変更は appsscript.json にて行う
(注意 3) Google Drive 上のスクリプトは管理しやすい場所へ

この手順はあくまで私がいつもやっている手順なので、もっとスマートなやり方があると思います。

# 9. FAQ

### Q1. コードのファイルを src ディレクトリに置くなら、`clasp create` する時に `--type` に加えて `--rootDir` も指定してやればいいのでは？

A. 期待するファイルが、期待するディレクトリに作られればそれが理想なのですが、`--rootDir` をした場合、`--rootDir` で指定したディレクトリに .clasp.json が作成されてしまいます。

```json
{
  "scriptId": "####################################################",
  "rootDir": "./src"
}
```
これだと `clasp push` が、プロジェクトのルート (package.json があるところ) いても、`--rootDir` で指定したディレクトリにいても、失敗します。
(clasp が想定する `--rootDir` を指定するシナリオが不明です。ちなみに `--rootDir` に指定するディレクトリを作事前に作っておく必要があるのは変わりません)

加えて、現状だと、どのようなオプションで　`clasp create` しようが、fileExtension 指定はできない (unknown option エラーになります) ので、.clasp.json の編集は必須で、だったらそこで rootDir を編集しちゃえば良い、という結論に至っています。

### Q2. `clasp open` してブラウザの GAS でファイルを作ってから `clasp pull` してローカルに持ってくるのってダサくない？いきなりローカルにファイルを書いて `clasp push` すれば効率が良いのでは？

「ダサい」については 100% agree です。しかし理由があります。

* `clasp push` のためには appsscript.json が必要になる
* 私は複数の Google アカウントを使っているので、 `clasp open` で適切なアカウントに紐付いているか確認できる
* ライブラリやサービスの設定は、ブラウザの GAS エディタ上で行った後、`clasp pull` でローカルに持ってくるのが安全で確実