GASをKotlin/JSで書こう！

# GASをKotlinで書きたい！

皆さんはGASとKotlinを利用されたことはありますか？
この両方を使ったことがある方であれば、「GASをKotlinで書けたら良いのにな～」と考えたことがあるかと思います。
私自身もそう思い、色々トライしていたのですが、Kotlin/JSの出力があまりに巨大で、GASでの利用を諦めていました。
しかし、最近リリースされたKotlin 1.4の新しいIRコンパイラを試してみると、これが非常に優秀で、出力が非常に小さくなっていることが分かりました。
過去にぶつかってしまった課題も解決したので、改めて挑戦したところ、なかなかいい感じに動作させることに成功しましたので、ここに共有します。

# 注意

「GASをKotlinで書きたい！」の項で触れている通り、この記事では執筆時点でalpha版な[新しいKotlin/JS IRコンパイラ](https://kotlinlang.org/docs/reference/js-ir-compiler.html)を利用します。今後、この記事の内容が正しくなくなる可能性も高いのでご注意ください。

# サンプルコード

こちらに既に動作を確認したサンプルコードを用意しました。
https://github.com/5hyn3/GasByKotlin

# GASをKotlinで動かしてみる

実際にGASをKotlinで動かしてみましょう！

## 環境の構築
以下の前提があります。

- WSL2上のUbuntu 20.04 LTSで作業を行っています
- IntelliJ IDEA Ultimate 2020.2を利用します
- Kotlin 1.4.0で動作確認を行っています
- node(v12.18.1), npm(v6.14.5)が導入済み


## 1. プロジェクトの作成

以下の画像のように、`Kotlin/JS for browser`を選択してプロジェクトを作成します
![a4bc6231ea6d38ed55196eb6eb766f49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105287/aa24900a-2074-1cd6-0816-10a93b0048af.png)


## 2. `gradle.properties`の編集

新しいKotlin/JS IRコンパイラを利用するので、以下を`gradle.properties`に追記します

```
kotlin.js.compiler=ir
```

## 3. `build.gradle`の編集

プロジェクトを生成したら `build.gradle`を以下のように編集します

```gradle:build.gradle
plugins {
    id 'org.jetbrains.kotlin.js' version '1.4.0'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-js"
    testImplementation "org.jetbrains.kotlin:kotlin-test-js"
}

kotlin {
    js {
        browser {
            webpackTask {
                output.libraryTarget = "commonjs"
            }

            testTask {
                useKarma {
                    useChromeHeadless()
                    webpackConfig.cssSupport.enabled = true
                }
            }
        }
        binaries.executable()
    }
}
```

ここで大事なポイントが

```gradle
            webpackTask {
                output.libraryTarget = "commonjs"
            }
```
の部分です。これが本記事で最も重要なポイントであると言っても過言ではありません。`Kotlin/JS`は出力するJavaScriptの形式を大まかに`ブラウザ向け`か`node向け`のどちらかで選択することができます。GASで動かしたいJavaScriptを出力するには、`node向け`を選択する必要があるのですが、新しいKotlin/JS IRコンパイラは、`ブラウザ向け`の場合にのみminifyを行います。`node向け`を選択するとminifyを行ってくれず、相変わらず巨大なソースコードが出力されることになります。しかし、`ブラウザ向け`の設定とすると、`window`など、ブラウザ依存のコードが出力されてしまい、GASで動作しないコードが出力されるようになってしまいます。
そこで、上記のように DSLで`browser`を指定しつつも、出力の設定を`commonjs`とすることで、minifyを有効にしつつ、ブラウザ依存のコードを出力しないように設定しています。
## 4. kotlinのコードを書く

`src/main/kotlin/main.kt`に適当なコードを書きます。
このドキュメントでは以下のようなコードを書いた体で進めます。

```Kotlin:main.kt
@JsExport
fun Hello() = "Hello"
```

JavaScript側に公開したいメソッドには`@JsExport`をつけてください。
つけ忘れるとビルドした際に利用されていないコードとして出力から削除されてしまいます。

## 5. ビルドする
`./gradlew build`でも、IntelliJ右上のビルドボタンでもどちらでも良いのでビルドを行います。
ビルド成功後、`build/distributions`内に、`${プロジェクトの名前}.js`ファイルが出力されていることを確認してください。

## 6. グルーコードの作成
GASはGlobalなファンクションをエントリポイントとして動作しますが、Kotlin/JSではJavaScriptの世界におけるGlobalなファンクションを定義することは今のところできません。そのため、100%Kotlin/JSだけでGASを動作させることは今の所不可能で、GASとKotlin/JSを繋げるJavaScriptによるグルーコードが必要となります。例えば、GAS側に`myFunction`という名前の関数を認識させて実行し、Kotlin/JSのコードの実行結果をログに保存させたい場合は以下のようになります。以下のコードは`GasByKotlin`という名前のプロジェクトの場合です。

```javascript:main.js
var GasByKotlin = require('../build/distributions/GasByKotlin'); //ここは5で確認したビルドによって出力されたファイルを指定する

global.myFunction = function() { // メソッドはこの形式で定義しないとGASから認識されません
    console.log(GasByKotlin.GasByKotlin.hello()) // JavaScriptでの変数名.${Kotlin側のプロジェクトファイルの名前}.${メソッド名}()でKotlin側のコードを呼び出せます
}
```


## 7. npmパッケージへの依存を追加する
これでコードの方はほぼ完成です。但し、当然このままではKotlin/JSのコードをGASに上げることはできないので、npmのパッケージへの依存を追加して、コードの変換やGASへのpushを行えるようにします。
`build.gradle`のdependenciesに以下を追記してください。

```gradle:build.gradle
dependencies {
    ...
    implementation npm("@google/clasp", "2.3.0") // GASのプロジェクトを作ったりpushしたりしてくれるGoogle公式パッケージ
    implementation npm("browserify", "16.5.2") // 以下のgasifyと合わせて、Kotlin/JSのコードをGASで動作するように変換させるために利用します
    implementation npm("gasify", "1.0.1") 
}
```
追記後 `./gradlew kotlinNpmInstall`を実行することで、`build/js/node_modules`にnpmパッケージがインストールされます。

## 8. claspのセットアップ
ビルドがうまくいったらプロジェクトのルートディレクトリでrootDirの設定をしつつclaspのセットアップをして適当なプロジェクトにpush出来る状態にしましょう。
7でclaspを`build/js/node_modules`にインストールしたため、`build/js/node_modules/.bin/clasp`でclaspを実行することができます。
claspについての解説は、他で取り扱っている記事がたくさんあるため、ここでは割愛します。

## 9. GAS向けにJSを変換する
現状のKotlin/JSによって出力されたJavaScriptには`export`が使われており、そのままではGASで利用することができません。そこで、browserifyとgasifyを利用して、このコードをGASでも動くように変換します。

```
build/js/node_modules/.bin/browserify ${6で作ったJavaScript} -p build/js/node_modules/gasify -o ${8で設定したclaspのrootDir}/{適当な名前}.js
```

## 10. pushする

```
build/js/node_modules/.bin/clasp push
```

## 11. 動作確認

GASのエディタを開いて、実際に動作を確認してみましょう。うまくいけば以下の画像のように7で作ったJavaScriptのメソッドが実行できるようになっており、実際に動作することを確認できます。
見ての通り、(横には長いですが)十行と少し程度のコンパクトなサイズにまとまっています。[^2]

[^2]: この画像で示されているコードは、変換前のコードが「決まった文字列を出力するだけのメソッド」なのでこれだけコンパクトになっています。とはいえ、以前のKotlinだと、たったそれだけのコードなのにKotlinのスタンダードライブラリのJS移植版がまるごとくっついてきてとても巨大になっていたので、相当に進化していることが伺えます。Kotlin側のコード規模に応じて変換後のコードも当然大きくなっていくのでご注意ください

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105287/a74137b9-8107-e88e-8db3-69b508630c0b.png)


## 12. ビルドをもうちょっと楽にする

Kotlinのコードを書いてから8, 9の操作を毎回行ってGASにコードを反映させる作業は面倒なため、`gradle`のタスクを定義してコマンド一つでビルドからpushまでを行ってくれるようにします。
以下を`build.gradle`に追記してください。

```gradle:build.gradle

task convertForGas(type: Exec) { //9で行った作業を定義しています
    executable "$buildDir/js/node_modules/.bin/browserify"
    args "${6で作ったJavaScript}", "-p", "$buildDir/js/node_modules/gasify", "-o", "${8で設定したclaspのrootDir}/{適当な名前}.js"
}

task push(type: Exec) { // 10で行った作業を定義しています
    executable "$buildDir/js/node_modules/.bin/clasp"
    args "push"
}

task buildAndPush {
    dependsOn build
    dependsOn convertForGas
    dependsOn push
}
```

このようにタスク定義をしておくことで、Kotlinのコードを変更するたびに

```bash
./gradlew buildAndPush
```

とコマンドを一つ実行するだけでKotlinのビルド、コードの変換、claspによるpushが行われるようになります。

## 13. Enjoy Kotlin!

ここまでできればKotlin/JSでGASのコードを実装する環境が整っています！どこでも動くKotlinでの開発を楽しみましょう！ :thumbsup: 

# おまけ dukatについて

Kotlin/JSにはTypeScriptの型をKotlin向けに変換してくれるdukatという仕組みがあり、Kotlin1.4.0からKotlin/JSに組み込まれています。
もちろん、TypeScriptをサポートしているGASにも用いることができ、**うまく動けば**型のサポートを得て補完などが効く快適なGAS開発環境を構築することができます。
この機能を利用するには、以下のように`build.gradle`のdependenciesに追記します。

```gradle:build.gradle
dependencies {
    ...
    implementation npm("@types/google-apps-script", "1.0.16", true)
}
```

`@types/google-apps-script`パッケージがGASのTypeScriptによる型定義を収めたもので、`npm`の第三引数にtrueを渡すことで、dukatによるKotlinコードの生成が有効になります。
これで最強のGAS開発環境ができた...！とは行かず、現状なぜか一部のコードの変換がうまく言っておらず、大量の`unresolved reference`が発生してしまいビルドエラーになってしまうようでした :sweat_drops: 

# 参考
- [Google Apps Scriptでrequire()してみる - Qiita](https://qiita.com/fossamagna/items/7c65e249e1e5ecad51ff)
- [Kotlin for JavaScript - kotlinlang] (https://kotlinlang.org/docs/reference/js-overview.html)