GASをKotlinで書きたい!
皆さんはGASとKotlinを利用されたことはありますか?
この両方を使ったことがある方であれば、「GASをKotlinで書けたら良いのにな~」と考えたことがあるかと思います。
私自身もそう思い、色々トライしていたのですが、Kotlin/JSの出力があまりに巨大で、GASでの利用を諦めていました。
しかし、最近リリースされたKotlin 1.4の新しいIRコンパイラを試してみると、これが非常に優秀で、出力が非常に小さくなっていることが分かりました。
過去にぶつかってしまった課題も解決したので、改めて挑戦したところ、なかなかいい感じに動作させることに成功しましたので、ここに共有します。
注意
「GASをKotlinで書きたい!」の項で触れている通り、この記事では執筆時点でalpha版な新しいKotlin/JS IRコンパイラを利用します。今後、この記事の内容が正しくなくなる可能性も高いのでご注意ください。
サンプルコード
こちらに既に動作を確認したサンプルコードを用意しました。
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
を選択してプロジェクトを作成します
2. gradle.properties
の編集
新しいKotlin/JS IRコンパイラを利用するので、以下をgradle.properties
に追記します
kotlin.js.compiler=ir
3. build.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()
}
}
ここで大事なポイントが
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
に適当なコードを書きます。
このドキュメントでは以下のようなコードを書いた体で進めます。
@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
という名前のプロジェクトの場合です。
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に以下を追記してください。
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のメソッドが実行できるようになっており、実際に動作することを確認できます。
見ての通り、(横には長いですが)十行と少し程度のコンパクトなサイズにまとまっています。1
12. ビルドをもうちょっと楽にする
Kotlinのコードを書いてから8, 9の操作を毎回行ってGASにコードを反映させる作業は面倒なため、gradle
のタスクを定義してコマンド一つでビルドからpushまでを行ってくれるようにします。
以下を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のコードを変更するたびに
./gradlew buildAndPush
とコマンドを一つ実行するだけでKotlinのビルド、コードの変換、claspによるpushが行われるようになります。
13. Enjoy Kotlin!
ここまでできればKotlin/JSでGASのコードを実装する環境が整っています!どこでも動くKotlinでの開発を楽しみましょう!
おまけ dukatについて
Kotlin/JSにはTypeScriptの型をKotlin向けに変換してくれるdukatという仕組みがあり、Kotlin1.4.0からKotlin/JSに組み込まれています。
もちろん、TypeScriptをサポートしているGASにも用いることができ、うまく動けば型のサポートを得て補完などが効く快適なGAS開発環境を構築することができます。
この機能を利用するには、以下のようにbuild.gradle
のdependenciesに追記します。
dependencies {
...
implementation npm("@types/google-apps-script", "1.0.16", true)
}
@types/google-apps-script
パッケージがGASのTypeScriptによる型定義を収めたもので、npm
の第三引数にtrueを渡すことで、dukatによるKotlinコードの生成が有効になります。
これで最強のGAS開発環境ができた...!とは行かず、現状なぜか一部のコードの変換がうまく言っておらず、大量のunresolved reference
が発生してしまいビルドエラーになってしまうようでした
参考
- Google Apps Scriptでrequire()してみる - Qiita
- [Kotlin for JavaScript - kotlinlang] (https://kotlinlang.org/docs/reference/js-overview.html)
-
この画像で示されているコードは、変換前のコードが「決まった文字列を出力するだけのメソッド」なのでこれだけコンパクトになっています。とはいえ、以前のKotlinだと、たったそれだけのコードなのにKotlinのスタンダードライブラリのJS移植版がまるごとくっついてきてとても巨大になっていたので、相当に進化していることが伺えます。Kotlin側のコード規模に応じて変換後のコードも当然大きくなっていくのでご注意ください ↩