TypeScriptを使ったGAS開発の環境構築

TypeScriptを使ってgas開発を行う際に、最低限必要になるであろう設定を備忘録的に残しておきます。
ソースコードは以下にあるので、手っ取り早く導入したい場合はそちらを使ってください。
https://github.com/shizukanaoki/gas_typescript_sample

開発環境
OS: macOS 10.14.4
Nodejs: v10.15.3

## プロジェクトの作成
兎にも角にも、ローカルにプロジェクトを作ります。
（yarnがない場合はinstallしましょう -> https://legacy.yarnpkg.com/lang/ja/docs/install/#mac-stable）

```
$ mkdir qiita_gas_typescript_sample
$ cd qiita_gas_typescript_sample/
$ yarn init --yes
```
## 必要なモジュールのインストール
webpackを使ってtypescriptをトランスパイルするために必要なモジュールをインストールします。

```
$ yarn add -D webpack webpack-cli typescript ts-loader
```

# webpackの設定
プロジェクト直下に`webpack.config.js`を作成し、設定を追加します。
`main.ts`をエントリーポイントに`bundle.gs`というファイルに固めます。

```js:webpack.config.js
module.exports = {
  entry: "./src/main.ts",
  output: {
    filename: "bundle.gs",
    path: `${__dirname}/dist`
  },
  resolve: {
    extensions: [".ts"]
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: "ts-loader"
      }
    ]
  }
};
```

# ts-loaderの設定
こちらもプロジェクト直下に`tsconfig.json`というファイルを作成します。
gasがes6に対応していないためtargetにes5を指定しています。

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015"
  }
}
```

# typescriptを書いてみる
期待した通りに動くか確認するため、一度動かしみましょう。
プロジェクト直下に`src`のディレクトリを作り、`main.ts`を作成しましょう。
内容はなんでもいいのですが、以下のようにしてみました。

```typescript:src/main.ts
class Main {
  public main(): void {
    console.log("hello world");
  }
}
```

# webpackしてみる
ここでwebpackを使ってtypescriptをjsに変換してみます。
`./node_modules/.bin`以下のwebpackコマンドを使用します。

```
$ ./node_modules/.bin/webpack
```

`dist/bundle.gs`が生成されれば成功です。

# コミットしてみる
ここまでのデータが消えてしまったら悲しいのでここら辺でコミットをしておきましょう。
（`.gitignore`を作成し、コミット対象から不要なファイルは除外しておきましょう。）

```text:.gitignore
/dist/*.gs
/node_modules/
```

```
$ git init
$ git add .
$ git commit
```

# claspしてみる
ここまででtypescriptをjsに変換するところまでできたので、このファイルをgasの環境にプッシュしましょう。
ここでは[clasp](https://github.com/google/clasp)を使用します。

まず、READMEにもありますが、claspを利用する為に、googleアカウントの設定を変更します。
https://script.google.com/home/usersettings にアクセスし、`Google Apps Script API` を`オン` に設定してください。その後、以下のコマンドを実行していきます。

```
$ yarn add -D @google/clasp
$ ./node_modules/.bin/clasp login
$ ./node_moduels/.bin/clasp create
// ? Create which script? -> 今回はformsで作成
// Created new Google Form: https://drive.google.com/open?id=xxxx
// Created new Google Forms Add-on script: https://script.google.com/d/xxxx/edit
// Warning: files in subfolder are not accounted for unless you set a '.claspignore' file.
// Cloned 1 file.
// └─ appsscript.json
```

google drive上に新規のGoogleFormとそれに紐づくscriptが作成されます。
また、`.clasp.json` と `appsscript.json`もプロジェクト直下に作成されます。

今回は`/dist`以下をプッシュの対象としたいので、`clasp.json`を以下のように書き換えます。
(`scriptId`は作成時に書かれているものをそのまま使ってください。)

```json:.clasp.json
{
  "scriptId": "xxxx", 
  "rootDir": "./dist"
}
```

また、`appsscript.json`はプッシュ対象のディレクトリの直下におく必要があるので`./dist`直下に移動しましょう。
ここまできてやっとプッシュできます。`Pushed 2 files.`と出力されればプッシュ成功です。

```
$ mv appsscript.json ./dist/appsscript.json
$ ./node_modules/.bin/clasp push
// └─ dist/appsscript.json
// └─ dist/bundle.gs
// Pushed 2 files.
```

# プッシュできたか確かめてみる
実際にプッシュできたか確認してみましょう。`.clasp.json`にある`scriptId`を使って以下のURLにアクセスしましょう。
`https://script.google.com/d/{scriptId}/edit`　

<img width="1021" alt="スクリーンショット 2020-01-26 14.22.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111555/4c797f8e-5cf6-a241-ae90-db1a469f92c5.png">

どうでしょう？プッシュできましたか？

# トリガーを作ってみる
プッシュできたのはいいものの、実際に作成したスクリプトをどのように実行するのでしょうか。
今回は、google formに紐付けたscriptを作成したので、formの送信時に実行されるトリガーを作成しましょう。

GASではトリガーとして実行する為に関数をトップレベルで宣言する必要があるのですが、今の状態だと関数スコープに閉じ込められてしまっていて実行できません。
[gas-webpack-plugin](https://github.com/fossamagna/gas-webpack-plugin)を使ってトップレベルで関数を宣言するようにしましょう。

```
$ yarn add -D gas-webpack-plugin
```

webpackの設定を以下のように書き換えてあげます。
プラグインとして`GasPlugin`を指定し、`production`モードで実行するように変更しています。

```js:webpack.config.js
const GasPlugin = require('gas-webpack-plugin');

module.exports = {
    mode: 'production',
    entry: './src/main.ts',
    output: {
        filename: 'bundle.gs',
        path: `${__dirname}/dist`,
    },
    resolve: {
        extensions: ['.ts'],
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: 'ts-loader',
            },
        ],
    },
    plugins: [new GasPlugin()],
};
```
main.tsは以下のようにしておきましょう。
`global`という名のオブジェクトを定義し、`main`プロパティに関数を代入しています。
こうすることでトップレベルに`main`という名の関数が定義されます。

```typescript:main.ts
declare const global {
  main: any
}

global.main = (e: any) => {
  console.log(e);
}
```

以下のコマンドを実行して、生成される`bundle.gs`を確認しましょう。

```
$ ./node_modules/.bin/webpack
```

`bundle.gs` の先頭で関数が定義されていれば成功です。
（引数のeには設定したトリガーに応じてイベントオブジェクトが入ってくるはずです。）

```js:bundle.gs
function main(e) {
}/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
// 以下略
```

# 再度、プッシュしてみる

トリガーとして実行できる関数を定義できたので、プッシュしてみましょう。

```
./node_modules/.bin/clasp push
```

再度、GASのエディタを開き、トリガーとして`main`関数を設定すれば準備完了。
`https://script.google.com/d/{scriptId}/edit`<img width="453" alt="スクリーンショット 2020-01-26 15.13.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111555/cf29333b-ee52-cfff-09b5-8fa4c54efc05.png">

ここまでくれば、あとは開発するのみ。小まめなコミットもお忘れなく！

# 快適な開発をしてみる
せっかくtypescriptを導入したので、GAS開発に必要な型が定義されたファイルを使って快適な開発をしましょう。

```
yarn install -D @types/google-apps-script
```

実際に、`main`関数を以下のように置き換えてみるとわかりますが、エディタのメソッド補完など様々な恩恵が受けられると思います。

```typescript:main.ts
declare const global: {
  main: any;
};

global.main = (e: any) => {
  const response: GoogleAppsScript.Forms.FormResponse = e.getResponse();
  response.getItemResponses().forEach(itemResponse => {
    console.log(itemResponse.getItem().getTitle());
  });
};
```

また、本記事の中で何度か実行した`./node_modules/.bin/`配下のコマンドは`package.json`に定義することで省略して実行することができます。

```json:package.json
{
  ...
  "scripts": {
    "push": "clasp push",
    "build": "webpack",
    "watch": "webpack -w",
    "cwatch": "clasp push --watch"
  },
  "devDependencies": {
    "@google/clasp": "^2.3.0",
    "@types/google-apps-script": "^1.0.10",
    "gas-webpack-plugin": "^1.0.2",
    "ts-loader": "^6.2.1",
    "typescript": "^3.7.5",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }
}
```

こうすることで以下のようにコマンドの入力を省略することができます。
他にも自身で使いやすいようにカスタマイズしてみてください。

```
$ yarn run push
-> gasにプッシュ
$ yarn run build
-> ビルドの実行
$ yarn run watch
-> typescriptファイルの変更を検知して自動でビルドしてくれる
$ yarn run cwatch
-> bundle.gsの変更を検知して自動でプッシュしてくれる
```