AndroidStudioでKotlin/JS(Node)を使う

# 概要

DartからNode.jsはアレですがkotlinはどうなのかなとKotlin/JSでやってみることにしました。

IDEにはAndroid Studioを使います。プラグインを入れればFlutterも使えますしKotlinの製造元のIDEがベースなので。

とはいえ結局は以下のサイトを参考にそのままではうまくいかなかったところを試行錯誤した結果を残しておこうという話です。

https://qiita.com/yt8492/items/1271b30ecd26f5ca0efb

# プロジェクトの作成

Android Studioの場合、Flutterを使うつもりがなくてもFlutterプラグインをインストールする必要があるようです。
なぜかというと素のままでは(ネイティブ)Androidプロジェクトしか新規作成できないようになっているからです。
Flutterプラグインを入れると「New Flutter Project」からAndroid以外のプロジェクトを作成できるようになります。

新規プロジェクトのGradleタブからKotlin/JSプロジェクトを作成できます、「Kotlin DSL ビルドスクリプト」「Kotlin/JS for Node.js」の2つにチェックを入れた状態にして「次へ」をクリックします。

「名前」に適当なプロジェクト名を入れて「完了」をクリックするとプロジェクトが作成されます。

しかしそのままではKotlinコードを書いても変換できないっぽいので設定ファイルを修正する必要があります。

# kotlinソースコードとフォルダの作成

Kotlinソースコードは`src\main\kotlin`フォルダを作成してその中に作成します。

# build.gradle.ktsの修正

細かいところは概要に貼ったサイトを参照ください、ここではexplessを使ったwebサーバーをkotlinで記述してjavascriptファイルを出力するのが目的になります。

```build.gradle.kts
plugins {
    id("org.jetbrains.kotlin.js") version "1.7.20"
}

group = "org.example"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
    jcenter() // 追加
}

dependencies {
    implementation(kotlin("stdlib-js"))
    implementation("org.jetbrains.kotlinx:kotlinx-nodejs:0.0.7") //追加
    implementation(npm("webpack-node-externals", "2.5.1"))

    //npmパッケージを追加したい場合はこのようにするとタスク実行時に追加してくれる
    implementation(npm("express", "4.18.2"))
}

kotlin {
    js {
        browser {
            webpackTask {
                outputFileName = "main.js"
                output.libraryTarget = "commonjs2"
            }
        }

        nodejs {
        }
    }
}
```

# webpack.config.jsの作成

webpack.config.dフォルダを作成してその中にwebpack.config.jsを作成します。

こちらも細かいことは概要に貼ったサイトを参照してもらうとして、サイトのコードのままではパスの関係なのかうまくnode_moduleのコードを除外してくれなかったので、`nodeExternal`関数にnode_modulesのパスを渡して除外されるようにしました。

```webpack.config.js
var nodeExternals = require('webpack-node-externals');
config.target = 'node';
config.mode = 'production';
config.externalsPresets = {node: true}
config.externals = [nodeExternals({modulesDir:__dirname + '\\..\\..\\node_modules'})];

//kotlinモジュールを出力JavaScriptに組み込む場合はこうする
//config.externals = [nodeExternals({modulesDir:__dirname + '\\..\\..\\node_modules', allowlist:['kotlin']})];

//minifyを無効にする場合はこうする
//config.optimization = {minimize: false}

//SourceMapを生成しないようにするにはこうする
//config.devtool = false;
```

# Android StudioでGradleタスクを実行

そのままではコマンドラインでgradleタスクを実行できない(一応JAVA_HOMEを設定すればできる)ので、Android StudioのGradleツールバーの「Gradleタスクの実行」ボタンからgradle browserWebpackを実行することでトランスパイルができます。

```
gradle browserWebpack
```

トランスパイルされたJavaScriptコードは`build\distributions`フォルダ内に作成されます。

# 実行

モジュール丸ごとパックしない場合、GradleタスクがインストールするNodeと実際に実行するNodeはおそらく別になるので(GradleタスクでNodeをインストールしてるっぽいんですがどこにあるんでしょう？)、出力したJavaScriptをNodeで実行するには実行する方のNodeにモジュールを追加する必要があります。

```batch
npm install kotlin express
```

モジュール丸ごとパックするのは正直おすすめできません。簡単なexpressのコードでも600KBくらいになりました・・・。

まぁNodeではnpmなりyarnなりでインストールすればいいだけなのでモジュール丸ごと一つのコードにまとめて実行なんて運用はまずしないし必要ないのでそんな人はいないでしょう。

# ちなみに

```main.kt
external fun require(module:String):dynamic

fun main() {
    println("Hello JavaScript!")

    val express = require("express")
    val app = express()

    app.get("/") { _, res ->
        res.type("text/plain")
        res.send("i am a beautiful butterfly")
    }

    app.listen(3000) {
        println("Listening on port 3000")
    }
}
```

適当に拾ってきたこんな感じのサンプル(ちょっといじってますが)をトランスパイルすると

```main.js
/******/ (() => { // webpackBootstrap
/******/ 	var __webpack_modules__ = ({

/***/ 966:
/***/ (function(module, exports, __webpack_require__) {

var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (root, factory) {
  if (true)
    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [exports, __webpack_require__(665)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
		__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
		(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
		__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  else {}
}(this, function (_, Kotlin) {
  'use strict';
  var println = Kotlin.kotlin.io.println_s8jyv4$;
  var Unit = Kotlin.kotlin.Unit;
  function main$lambda(f, res) {
    res.type('text/plain');
    return res.send('i am a beautiful butterfly');
  }
  function main$lambda_0() {
    println('Listening on port 3000');
    return Unit;
  }
  function main() {
    println('Hello JavaScript!');
    var express = __webpack_require__(860);
    var app = express();
    app.get('/', main$lambda);
    app.listen(3000, main$lambda_0);
  }
  _.main = main;
  main();
  return _;
})); 
//# sourceMappingURL=testexpress.js.map


/***/ }),

/***/ 860:
/***/ ((module) => {

"use strict";
module.exports = require("express");

/***/ }),

/***/ 665:
/***/ ((module) => {

"use strict";
module.exports = require("kotlin");

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
/******/ 	
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module is referenced by other modules so it can't be inlined
/******/ 	var __webpack_exports__ = __webpack_require__(966);
/******/ 	module.exports.testexpress = __webpack_exports__;
/******/ 	
/******/ })()
;
//# sourceMappingURL=main.js.map
```

こんな感じになります(設定でminifyを無効にしたもの、デフォルトではminifyされます)。ちょっと長いですが100KB超のコードを強制的に引っ付けるDartよりはマシですね(多分コードの大半はwebpackがつけたものっぽいし)。

ちなみにKotlinモジュールを付けてトランスパイルした場合のサイズは40KBくらいでした(minify時)。実行に大規模なランタイムコードが必要なトランスパイルはもう世の中からは受け入れられないんじゃないかとは思いますが一応Dartよりはまだマシですね。

# githubリポジトリ

手動でファイルの作成や修正をしないで済むように最低限の設定とファイルの作成を済ませてあるプロジェクトファイル群をgithubにアップロードしました。
クローンするなりZIPダウンロード・展開してインポートするなりするだけで(初期ビルドに時間がかかるっぽいのですぐにとはいきませんが)Kotlin/JS(Node用)の開発をAndroid Studioで始めることができます。

https://github.com/uniras/KotlinNodeMinimumWebpack