Google App Scriptを自分のマシンで開発できる google clasp を使ってみる・前編

社内向け発表資料をQiitaに投稿。

# GAS (Google App Script) とは

## GASの特徴

ChatGPTさんに尋ねてみるとこんな事いってきます。

```
Google App Script（グーグルアプスクリプト）とは、Googleが提供しているプログラミングツールです。
これを使うと、Googleのさまざまなアプリ（たとえば、Gmail、Googleドライブ、Googleスプレッドシートなど）をもっと便利にするためのプログラムを書けるようになります。
```

- 定期的にスクリプトを実行でき、決まった処理を自動化することができる
- スプレッドシートに格納されたデータを処理することができる
- ウェブブラウザとGoogleのアカウントがあれば簡単に始められる
- 利用は基本的に無料
- Googleのサービスと簡単に連携することができる

## GASのデメリット

- 実行時間に制限がある
    - スクリプトの実行時間は１回の処理につき最大6分
        - 6分を超えたらスクリプトが強制終了される
- JavaScriptベースのプログラミング言語である
    - Python等に比べると配列の処理に不便
    - デバッグが難しい
    - TypeScriptに慣れてると型チェックがない言語で使いづらい
- テストが書けない・書きづらい
- Googleのサービスに依存している
    - たまに起こるGoogleサービス停止が発生すると、なんにもできん
- 開発・実行するにはインターネット接続が不可欠
- Git等でコードをバージョン管理できない

# google clasp

これらGASのデメリットをちょびっとだけ改善できる Google 謹製のプロジェクト・google clasp（Command Line Apps Script Projects）を使ってみる。

## 特徴: ローカルの環境で好きなエディタを使ってGASを開発

claspを使うと、Google Apps ScriptのコードをローカルマシンのテキストエディタやIDE（統合開発環境）で編集できるようになる。

これによりVS Codeなどの高度なエディタの機能を活用して、効率的に開発が可能になる。

ローカルの環境でコードは書けるけど、実行環境はあくまでクラウドのGAS側。GASにコードをpushしないと動作の確認ができないので注意。

## 特徴: 書いたコードをバージョン管理できる

ローカルの環境でコードを保存できるため、Gitなどのバージョン管理システムでコードを管理することもできる。これにより、コードに対して行った変更履歴を追跡可能となる。また、複数人での共同開発や過去の変更の管理も容易になる。

## 特徴: 既存のGASもローカル環境にインポートできる

既に開発済みのGASもローカルの環境に保存することができる。

## 特徴: ローカルの環境からデプロイできる

GASの実行環境に対し、pushやデプロイは以下のコマンドを入力するだけ。

```
$ clasp push
$ clasp deploy -i AKfyAAAvqg
```

## 特徴: コード整形、Lint、ユニットテストを外部ツールを使って行うことができる

ESLintなどのコード解析ツールや、Jestなどのテストフレームワークと組み合わせて使用することが可能。

## 特徴: TypeScriptが使える

claspではTypeScriptで開発ができる。TypeScriptの静的型付けにより、コードの安全性や可読性の向上が期待できる。

ただ、GASの実行環境はTypeScriptがそのまま動かせない。GASのpush・デプロイ時、JavaScriptにトランスエンコードしたものをデプロイすることができる。

# claspの開発手順

## プロジェクトの初期化

以下のコマンドで初期化。よろずバージョン管理ツール・asdfを利用して nodejs の環境を構築。

```bash
$ asdf local nodejs 22.2.0
$ npm i @google/clasp
$ clasp login

https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fscript.deployments%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fscript.projects%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fscript.webapp.deploy%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fservice.management%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Flogging.read%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform&response_type=code&client_id=xxxxxxxxxxxxx-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A61078&service=lso&o2v=2&ddm=0&flowName=GeneralOAuthFlow
```

## プロジェクト作成

`clasp create` でプロジェクトの作成。ここでは standalone を選択してGASを構築。

```
$ clasp create calc_sample --rootDir ./src
(node:27646) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
? Create which script? (Use arrow keys)
❯ standalone 
  docs 
  sheets 
  slides 
  forms 
  webapp 
  api 

? Create which script? standalone
Created new standalone script: https://script.google.com/d/1bNq6bqZpsYxHaT0_FvADKz0I4iyh5_GLaS9Gt9iu3UFL7bvD76q8ivlW/edit
Warning: files in subfolder are not accounted for unless you set a 'src/.claspignore' file.
Cloned 1 file.
└─ ./src/appsscript.json
```

このあとできるファイル類。

```bash
drwxr-xr-x   10 yusuke  staff     320  7  1 20:49 .
drwxr-xr-x   11 yusuke  staff     352  7  1 20:36 ..
-rw-------    1 yusuke  staff      91  7  1 20:35 .clasp.json
-rw-r--r--    1 yusuke  staff      14  6 30 18:42 .tool-versions
drwxr-xr-x  221 yusuke  staff    7072  7  1 20:09 node_modules
-rw-r--r--    1 yusuke  staff  113315  7  1 20:09 package-lock.json
-rw-r--r--    1 yusuke  staff     282  7  1 20:20 package.json
drwxr-xr-x    6 yusuke  staff     192  7  1 20:49 src

## src 以下
yusuke@suzuki-m2-Mac-mini calculate_sample % ls -al src
total 32
drwxr-xr-x  6 yusuke  staff  192  7  1 20:49 .
drwxr-xr-x  9 yusuke  staff  288  7  1 20:57 ..
-rw-r--r--  1 yusuke  staff  119  7  1 20:38 appsscript.json
```

src/appsscript.json の中身

```bash
{
  "timeZone": "America/New_York", # > "Asia/Tokyo" に変更
  "dependencies": {
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}
```

.clasp.json の中身

```bash
{"scriptId":"1bNq6bqZpsYxHaT0_FvADKz0I4iyh5_GLaS9Gt9iu3UFL7bvD76q8ivlW","rootDir":"./src"}
```

## GAS 側を開いてみる

このスクリプトが関連しているGASを以下のコマンドで開く。

```bash
$ clasp open
```

まだ何も push してないので、中身は空。

また作成時に指定したのがスタンドアローンスクリプトなので、関連する SpreadSheet も存在してない。

![%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88_2024-07-01_20.06.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/d258f018-4551-9a36-292e-0d27e5a3551a.png)


## TypeScriptを書いてみる

claspでTypeScriptで開発するには、下準備が必要。

```bash
$ npm install --save-dev typescript @types/google-apps-script

added 4 packages, changed 1 package, and audited 279 packages in 2s

86 packages are looking for funding
  run `npm fund` for details
```

tsconfig.jsonは特に要らない。

TypeScriptのコードを書いてみる。srcディレクトリに Main.tsを作る。

src/Main.ts

```tsx
// Main.ts

function add(a: number, b: number): number {
  return a + b;
}

function subtract(a: number, b: number): number {
  return a - b;
}

function logMessage(message: string): void {
  Logger.log(message);
}

function main() {
  const resultAdd = add(5, 3);
  const resultSubtract = subtract(5, 3);

  logMessage(`Addition Result: ${resultAdd}`);
  logMessage(`Subtraction Result: ${resultSubtract}`);
}
```

## TypeScriptをGASにpushしてみる

package.jsonをこんな感じに変更する。

package.json

```tsx
{
  "name": "clasp-calculate-sample",
  "version": "1.0.0",
  "dependencies": {
    "@google/clasp": "^2.4.2"
  },
  "devDependencies": {
    "@types/google-apps-script": "^1.0.83",
    "typescript": "^5.5.2"
  }
}
```

clasp をpushすることで、claspが自動的にTypeScriptをGASにトランスコンパイルしてから、GASの実行環境に転送してくれる。

```tsx
$ clasp push      

(Use `node --trace-deprecation ...` to show where the warning was created)
└─ src/appsscript.json
└─ src/Main.ts
Pushed 2 files.
```

TypeScriptがトランスコンパイルされ、変換されたGASのコードはこんな感じ。

```tsx
// Compiled using clasp-calculate-sample 1.0.0 (TypeScript 4.9.5)
// Main.ts
function add(a, b) {
    return a + b;
}
function subtract(a, b) {
    return a - b;
}
function logMessage(message) {
    Logger.log(message);
}
function main() {
    var resultAdd = add(5, 3);
    var resultSubtract = subtract(5, 3);
    logMessage("Addition Result: ".concat(resultAdd));
    logMessage("Subtraction Result: ".concat(resultSubtract));
}

```

main関数を実行してみる。

![%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88_2024-07-01_21.09.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/adb03305-3a5a-90a6-fae8-9739d5dcc7d4.png)


# claspの欠点

ここまでの説明にて、一見便利に見える clasp だけども、いくつかの欠点も存在する。

## TypeScriptのコード分割記法 import 構文が機能しない

- 機能するようにするには webpack を使うなど、いくつか方法がある
- が、package.json を変更・定義するのがめんどくさい

## TypeScriptの型チェックがデプロイ時に機能するわけではない。

例えば add 関数の引数に文字列を設定した場合、TypeScriptのチェックが入るテキストエディタでは型チェックが入り、引数の型が間違ってるよーと怒られる。
    
![%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88_2024-07-01_21.17.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/fde99ff2-de5e-e06f-1a09-fe58994d41c2.png)

が、エディタで警告されたからといって、clasp push によるデプロイが失敗するわけではない。なので、push後、に反映されたGASを実行すると、普通に文字列の足し算が実行されちゃうという悲しみ。

![%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88_2024-07-01_21.21.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18599/35d18554-4c11-be36-6101-0705c4a140ae.png)
        

## コード整形、lint、ユニットテスト等の環境を自分で用意する必要がある

ファイル構成が自由であるが故、開発者それぞれで設定のやり方が異なり、こうすべきであるといった決まったやり方が定まっていない。

これらのデメリットを改善する方法は一応用意されてるが、後編にて。

# まとめ

- 素のGASを書くのは色々とツライ。が、claspを使うとメリットが色々享受できる
- claspは便利。だが自由度が高すぎて環境整えるのが大変

以上