同じくハマった人にたどり着いてほしくて書いた。
この記事のまとめ
-
clasp push
実行時にSyntax error: ...
と出たら、それはclaspで変換されたスクリプトが、GAS環境では読めないことを意味する。 - この場合、
tsconfig.json
のcompilerOptions
のtarget
プロパティを調整し、GASが読めるバージョンまで下げてあげると良い。 - 具体的には、2024年3月現在では、
"target": "es2021"
ではうまくいった。 - ちなみに、読めていないsyntaxは、クラス内のプロパティ宣言だった。
イントロ。何が起きたのか。
- GAS(Google Apps Script)はGoogleスプレッドシートやその他のツールチェーンを扱うためのスクリプト言語および実行環境。GASはJavaScriptと互換性がある文法なのが嬉しい。
- 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はどのようにファイルを変換していたか
- プロジェクトの
tsconfig.json
を読み込んで、それに従って変換していた。 - 変換の具体的処理はts2gasというライブラリが担っていた。
- ts2gasは、指定された
tsconfig.json
に従ってのTSからJSへの変換以外に、GASでの特殊処理をAST操作で行っていた
GASが吐くエラーの内容
- スクリプトのアップロードAPIのHTTPレスポンスで
400 Bad Request
と返されている。 - 原因は、アップロードしたファイルの文法エラーであった。
- 具体的な文法エラーは下記のようなクラス構文であった。
class Foo {
prop1; // これが駄目らしい
}
いや、こんなん書かないよ?
わかる。JSにしたら意味ないしね。ただTypeScriptの下記のようなコードが、上に変換されるのだ。
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で動くようになったらしいので、classだけ読めないやつ、みたいな中途半端なJS実装になっているわけではないと考えている
GASとTypeScriptについての考察
- 僕はUniversal JavaScriptを夢見てやまない人なので、GASだろうがなんだろうがTypeScriptで書いてなんぼだと思っている。これは過激派の意見だ。
- ただ、GASはサクッと書いて終わり、みたいな用途で使う人が多いだろうから、そういう場合は、ChatGPTなりに書いてもらってコピペしたらいい。
- 本格的なコード管理をしたいGASプロジェクトがあるとしたら、現時点での選択肢はclaspになるかもしれないが、僕は別なことを考えている。
- GASがV8 runtimeを採用した結果、特殊な変換はほとんど不要になった。ブラウザのJSみたいなもん。
- そうすると、bundlerのエコシステムに乗っかるほうがいいかも。
- GASへのアップロードとか、そのへんのめんどくさいところだけ切り出して、ファイル変換そのものはブラウザ的な世界観で扱えるようになったりしないかな。
-
UrlFetchApp
とかの仕様をwrapして拡張Universalとかしたいよね。 - 僕そのへん好きだから、なんか作るかも、ご期待あれ。
- この辺、詳しくは別記事に。
まとめ(再掲)
-
clasp push
実行時にSyntax error: ...
と出たら、それはclaspで変換されたスクリプトが、GAS環境では読めないことを意味する。 - この場合、
tsconfig.json
のcompilerOptions
のtarget
プロパティを調整し、GASが読めるバージョンまで下げてあげると良い。 - 具体的には、2024年3月現在では、
"target": "es2021"
ではうまくいった。 - ちなみに、読めていないsyntaxは、クラス内のプロパティ宣言だった。