clasp pushでエラーが起きたらtsconfig.jsonのtargetを下げよ。

同じくハマった人にたどり着いてほしくて書いた。

# この記事のまとめ
1. `clasp push`実行時に`Syntax error: ...`と出たら、それはclaspで変換されたスクリプトが、GAS環境では読めないことを意味する。
1. この場合、`tsconfig.json`の`compilerOptions`の`target`プロパティを調整し、GASが読めるバージョンまで下げてあげると良い。
1. 具体的には、2024年3月現在では、`"target": "es2021"`ではうまくいった。
1. ちなみに、読めていないsyntaxは、クラス内のプロパティ宣言だった。

# イントロ。何が起きたのか。
* GAS(Google Apps Script)はGoogleスプレッドシートやその他のツールチェーンを扱うためのスクリプト言語および実行環境。GASはJavaScriptと互換性がある文法なのが嬉しい。
* [clasp](https://github.com/google/clasp)はGASをローカル環境でTypeScriptを使って開発することをサポートするライブラリで、TypeScriptをGAS用のファイルに変換してくれる。
* claspで生成したGAS用のファイルを、Googleの実行環境にアップロードするコマンドが`clasp push`であるが、この度、これを実行したときに下記のようなエラーに遭遇した。

```
axiosError: Syntax error: ParseError: Unexpected token = line: 26 file: foo.gs
    at Gaxios._request 
(中略)
  response: {
    config: {
      url: 'https://script.googleapis.com/v1/projects/(SCRIPT_ID)/content',
      method: 'PUT',
      userAgentDirectives: [Array],
      paramsSerializer: [Function (anonymous)],
      data: [Object],
      headers: [Object],
      params: {},
      validateStatus: [Function (anonymous)],
      retry: true,
      body: (中略)      
    status: 400,
    statusText: 'Bad Request',
   (略)
```

* この原因を数時間(つらい)調査した結果、ちゃんと解釈できるかたちの知見を得たんで紹介してく。



# 調べたこと
* claspがどのようにファイルを変換しているのか
* GASが吐くエラーの内容
* GASはなぜエラーを吐いたか
* どうすればエラーは起きなくなるか


# 分かったこと
## claspはどのようにファイルを変換していたか
1. プロジェクトの`tsconfig.json`を読み込んで、それに従って変換していた。
1. 変換の具体的処理は[ts2gas](https://github.com/grant/ts2gas)というライブラリが担っていた。
1. ts2gasは、指定された`tsconfig.json`に従ってのTSからJSへの変換以外に、GASでの特殊処理をAST操作で行っていた

## GASが吐くエラーの内容
1. スクリプトのアップロードAPIのHTTPレスポンスで`400 Bad Request`と返されている。
1. 原因は、アップロードしたファイルの文法エラーであった。
1. 具体的な文法エラーは下記のようなクラス構文であった。

```ts
class Foo {
  prop1; // これが駄目らしい
}
```

### いや、こんなん書かないよ？
わかる。JSにしたら意味ないしね。ただTypeScriptの下記のようなコードが、上に変換されるのだ。

```ts
class Foo {
  private prop1: string; // 宣言しておかないとTSコンパイラは怒ってくる
  constructor(prop1: string) {
    this.prop1 = prop1;
  }
```

TSでは、プロパティをクラス直下に宣言しておかないと怒られるので、仕方なく宣言した結果、
今度はGAS側で、これが読めなくなってしまうのだと。

## GASはなぜエラーを吐いたか
直接の回答は、上述のとおり、「クラス構文が読めなかったから」だが、ここでは**なぜGASはクラス構文が読めなかったのか**を記す。

ここからは調べたわけではないが、推測で、**ES2022に準拠していないから**ではないか(2024.3.20時点)と考えた。なぜならクラス直下にプロパティを宣言できるようになったのはES2022からのようだからだ。https://github.com/tc39/proposal-class-fields

## どうすればエラーは起きなくなるか
```tsconfig.json
{  "compilerOptions": 
    { "target": "es2021" }
}
```
(手書きしたのでコピペはしないで)

* このように`tsconfig.json`で、ES2022より下のターゲットにしたらうまくいった。
* ES2021でうまくいった。
* 例えばES2017以降にあるような`Object.entries()`のようなユーティリティはGASでも使えるようなので、それよりはあとの年代のESバージョンを指定してよいはずだ。
* 確か、ある時を境に[GASはV8 Runtimeで動くようになった](https://workspace.google.com/blog/ja/developers-practitioners/data-processing-just-got-easier-apps-scripts-new-v8-runtime)らしいので、classだけ読めないやつ、みたいな中途半端なJS実装になっているわけではないと考えている

# GASとTypeScriptについての考察
* 僕はUniversal JavaScriptを夢見てやまない人なので、GASだろうがなんだろうがTypeScriptで書いてなんぼだと思っている。これは過激派の意見だ。
* ただ、GASはサクッと書いて終わり、みたいな用途で使う人が多いだろうから、そういう場合は、ChatGPTなりに書いてもらってコピペしたらいい。
* 本格的なコード管理をしたいGASプロジェクトがあるとしたら、現時点での選択肢はclaspになるかもしれないが、僕は別なことを考えている。
  * GASがV8 runtimeを採用した結果、特殊な変換はほとんど不要になった。ブラウザのJSみたいなもん。
  * そうすると、bundlerのエコシステムに乗っかるほうがいいかも。
  * GASへのアップロードとか、そのへんのめんどくさいところだけ切り出して、ファイル変換そのものはブラウザ的な世界観で扱えるようになったりしないかな。
  * `UrlFetchApp`とかの仕様をwrapして拡張Universalとかしたいよね。
  * 僕そのへん好きだから、なんか作るかも、ご期待あれ。
  * この辺、詳しくは別記事に。

# まとめ(再掲)
1. `clasp push`実行時に`Syntax error: ...`と出たら、それはclaspで変換されたスクリプトが、GAS環境では読めないことを意味する。
1. この場合、`tsconfig.json`の`compilerOptions`の`target`プロパティを調整し、GASが読めるバージョンまで下げてあげると良い。
1. 具体的には、2024年3月現在では、`"target": "es2021"`ではうまくいった。
1. ちなみに、読めていないsyntaxは、クラス内のプロパティ宣言だった。