GAS の CLI ツール「clasp」で TypeScript を扱ってみた

# はじめに

いつか触ろうと思って触ってなかった clasp
https://qiita.com/HeRo/items/4e65dcc82783b2766c03
を参考にしながら触ってみた。

しかも [@google/clasp](https://github.com/google/clasp) v1.5.0 から TypeScript をサポートしていて、
トランスパイルも自動的にやってくれるとのこと。


但し、TS ファイルから自動的に GS ファイルに変換されたものを
`clasp clone` や `clasp pull` すると、変換後の結果が JS ファイルとしてダウンロードされるらしい。
ので、最初からトランスパイル後のファイルを `clasp push` したら良いのかな？と試してみた記録です。

# 前提

複数ファイルに分割していない場合は直接 push しても、
トランスパイル後のファイルを push しても変わりはなかったので、
複数ファイルを用意して試す。

## push 対象の TS ファイル

```ts:src/code.ts
import { outputLog } from './hoge';

function myFunction() {
  outputLog();
}
```

```ts:src/hoge.ts
export function outputLog(): void {
  Logger.log('Hello World!');
}
```

# :white_check_mark: TS ファイルを直接 clasp push した場合

初めに普通にそのまま push するパターン
`.clasp.json` の rootDir で push 対象ディレクトリを指定。

```bash
$ clasp push
└─ src/appsscript.json
└─ src/code.ts
└─ src/hoge.ts
Pushed 3 files.
```

## 変換された GS ファイル

```js:code.gs
// Compiled using ts2gas 3.4.4 (TypeScript 3.6.2)
var exports = exports || {};
var module = module || { exports: exports };
//import { outputLog } from './hoge';
function myFunction() {
    outputLog();
}
//# sourceMappingURL=module.js.map
```

↑import 文が勝手にコメントアウトされてた。
`ts2gas 3.4.4` っていうのを使ってるみたい？

```js:hoge.gs
// Compiled using ts2gas 3.4.4 (TypeScript 3.6.2)
var exports = exports || {};
var module = module || { exports: exports };
function outputLog() {
    Logger.log('Hello World!');
}
exports.outputLog = outputLog;
//# sourceMappingURL=module.js.map
```

### myFunction 実行結果

<img width="708" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53393/13370ca0-4fd4-cfd1-b8ff-d284ae93d1fc.png">

まぁ普通にいける

# トランスパイルした JS ファイルを clasp push した場合

tsconfig.json で `dist` 以下にトランスパイルファイルを出力するようにした。
合わせて `.clasp.json` の rootDir は `dist` に変更した。

## :x: module="es2015", target="es5"

```bash
$ clasp push
| Pushing files...Push failed. Errors:
{ Error: Syntax error: Missing ; before statement. line: 1 file: code
    at Gaxios.request (/Volumes/sdcard/Bin/.nodenv/versions/10.15.1/lib/node_modules/@google/clasp/node_modules/gaxios/build/src/gaxios.js:70:23)
    at process._tickCallback (internal/process/next_tick.js:68:7)
  response:
   { config:
      { url:
         'https://script.googleapis.com/v1/projects/xxxxxxxxxx/content',
        method: 'PUT',
        paramsSerializer: [Function],
        data: [Object],
        headers: [Object],
        params: [Object: null prototype] {},
        validateStatus: [Function],
        retry: true,
        body:
         '{"scriptId":"xxxxxxxxxx","files":[{"name":"appsscript","type":"JSON","source":"{\\n  \\"timeZone\\": \\"Asia/Tokyo\\",\\n  \\"dependencies\\": {\\n  },\\n  \\"exceptionLogging\\": \\"STACKDRIVER\\"\\n}"},{"name":"code","type":"SERVER_JS","source":"import { outputLog } from \'./hoge\';\\nfunction myFunction() {\\n    outputLog();\\n}\\n//# sourceMappingURL=code.js.map"},{"name":"hoge","type":"SERVER_JS","source":"export function outputLog() {\\n    Logger.log(\'Hello World!\');\\n}\\n//PropertiesService.getScriptProperties().getProperty(\'HOGE\')\\n//# sourceMappingURL=hoge.js.map"}]}',
        responseType: 'json',
        retryConfig: [Object] },
     data: { error: [Object] },
     headers:
      { 'alt-svc': 'quic=":443"; ma=2592000; v="46,43,39"',
        'cache-control': 'private',
        connection: 'close',
        'content-encoding': 'gzip',
        'content-type': 'application/json; charset=UTF-8',
        date: 'Mon, 09 Sep 2019 04:43:21 GMT',
        server: 'ESF',
        'transfer-encoding': 'chunked',
        vary: 'Origin, X-Origin, Referer',
        'x-content-type-options': 'nosniff',
        'x-frame-options': 'SAMEORIGIN',
        'x-xss-protection': '0' },
     status: 400,
     statusText: 'Bad Request' },
  config:
   { url:
      'https://script.googleapis.com/v1/projects/xxxxxxxxxx/content',
     method: 'PUT',
     paramsSerializer: [Function],
     data:
      { scriptId: 'xxxxxxxxxx',
        files: [Array] },
     headers:
      { 'Accept-Encoding': 'gzip',
        'User-Agent': 'google-api-nodejs-client/2.0.4 (gzip)',
        Authorization:
         'Bearer ya29.Glx-B1azGu5jyQ_tkABZ9n6mmnDuPnn5uZLM44sOwDMXfAt3mC7Bem5Dmi9joKJ3lmvg7vNy87f37h0muiiccVOdO1Uv-n4YQLqLasp5r03_EzZZAlQ1_AHCt_70bg',
        'Content-Type': 'application/json',
        Accept: 'application/json' },
     params: [Object: null prototype] {},
     validateStatus: [Function],
     retry: true,
     body:
      '{"scriptId":"xxxxxxxxxx","files":[{"name":"appsscript","type":"JSON","source":"{\\n  \\"timeZone\\": \\"Asia/Tokyo\\",\\n  \\"dependencies\\": {\\n  },\\n  \\"exceptionLogging\\": \\"STACKDRIVER\\"\\n}"},{"name":"code","type":"SERVER_JS","source":"import { outputLog } from \'./hoge\';\\nfunction myFunction() {\\n    outputLog();\\n}\\n//# sourceMappingURL=code.js.map"},{"name":"hoge","type":"SERVER_JS","source":"export function outputLog() {\\n    Logger.log(\'Hello World!\');\\n}\\n//PropertiesService.getScriptProperties().getProperty(\'HOGE\')\\n//# sourceMappingURL=hoge.js.map"}]}',
     responseType: 'json',
     retryConfig:
      { currentRetryAttempt: 0,
        retry: 3,
        retryDelay: 100,
        httpMethodsToRetry: [Array],
        noResponseRetries: 2,
        statusCodesToRetry: [Array] } },
  code: 400,
  errors:
   [ { message:
        'Syntax error: Missing ; before statement. line: 1 file: code',
       domain: 'global',
       reason: 'badRequest' } ] }
└─ dist/appsscript.json
└─ dist/code.js
└─ dist/hoge.js
```

↑なんかエラー出た。

### push しようとしたトランスパイル結果

```js:dist/code.js
import { outputLog } from './hoge';
function myFunction() {
    outputLog();
}
//# sourceMappingURL=code.js.map
```

```js:dist/hoge.js
export function outputLog() {
    Logger.log('Hello World!');
}
//# sourceMappingURL=hoge.js.map
```

↑普通に import/export なやつ。

---

## :x: module="commonjs", target="es5"

```bash
$ clasp push
└─ dist/appsscript.json
└─ dist/code.js
└─ dist/hoge.js
Pushed 3 files.
```

↑clasp push できた

### トランスパイル結果を push して変換された GS ファイル

```js:code.gs
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var hoge_1 = require("./hoge");
function myFunction() {
    hoge_1.outputLog();
}
//# sourceMappingURL=code.js.map
```
```js:hoge.gs
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function outputLog() {
    Logger.log('Hello World!');
}
exports.outputLog = outputLog;
//# sourceMappingURL=hoge.js.map
```

↑requre 系なやつ。
ちなみに dist 以下に出力された `dist/code.js` と `dist/hoge.js` と中身は同じ。
ファイル名だけ `.gs` に変換されている模様

### myFunction 実行結果

<img width="931" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53393/4bbc4776-3c35-0932-f0db-d267cbf84826.png">

実行ができなかった！

# まとめ

import を使わない場合はトランスパイル後の JS ファイルを push しても問題はない。
でも clasp に任せて TS ファイルを直接 push したほうが良さそう。

push した TS ファイルは GS ファイルに変換されているので、
次回 `clasp clone` , `clasp pull` 等をすると変換後の GS ファイルがダウンロードされてしまう。
TS ファイルは別途、Git 等で管理したほうが良さそう。