kibana pluginを独立したReact Appとして開発する方法


# はじめに

## kibana plugin開発サイクルが非常に遅い問題

kibana pluginを開発していると、コードを修正して画面で確認、という基本的な開発サイクルに非常に時間がかかる問題にぶつかりました。

主な原因はkibana本体を含んだ開発環境にありそうだったので、それを解決する手段として、kibana本体から独立したアプリ（以下、独立用アプリ）とする方法を試し、劇的に速度改善したので、その備忘録です。

## ゴール

開発サイクル上、kibana pluginがkibana本体に依存しているものとして

* ビルド環境
* Elastic UI framework
* Elasticsearchとの通信処理

がありますが、これらをkibana本体無しで動作させ、開発サイクルを速くするのがゴールです。

## 独立用アプリの構築方針

* すでに実装済みのkibana pluginアプリを動作する環境を構築する
* 上記アプリをkibana本体から引き剥がすのではなく、ゼロから開発環境を構築して同アプリが動くようにする
* 開発サイクルとしての想定は、ある程度出来上がるまで独立用アプリで開発し、まとまった段階で本来のkibana pluginのコードにマージして動作確認する


環境構築手法による差異が大きいと思われるため、細かい手順は端折って、考慮すべきポイントを残しておきます。

# ビルド環境

## Node.js + React.js

kibana pluginはNode.js（サーバ側）とReact.js（クライアント側）で構成されていますので、まず、その環境が必要になります。Reactに慣れてなかったので、この環境構築手段があれこれあって迷いました。

## Next.js

今回は、開発環境だけで良くて（プロダクション環境等は不要で）簡単に上記環境が動かせる方法として、[Next.js](https://nextjs.org/)を使って構築しました。
なお、今回のアプリは、Node.js側はElasticsearch APIを叩くだけの処理しかなかったので、ほぼReact.jsでの実装となっています。

そこで、それっぽいNext.jsのsample [api-routes-rest](https://github.com/vercel/next.js/tree/canary/examples/api-routes-rest)を参考に構築を進めました。

## クライアントサイドレンダリング

環境構築後、まるっとkibana plugin側のアプリコード（主に`public/`フォルダ）を移行したところ、ビルドで以下のようなエラーがでました。

```
ReferenceError: window is not defined
```

Next.jsは基本サーバサイドレンダリングになるで、クライアントでしか動かないコードはエラーになるわけですね。

### 解決手段

Next.jsはクライアントサイドレンダリング用の[書き方](https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr)を用意しています。

具体的なコードは以下です。

```javascript:page/index.js
// このままだとMainコンポーネントはサーバサイドレンダリングになる
// import Main from "../public/components/main_component"

// Mainコンポーネントをクライアントサイドレンダリングにする
import dynamic from 'next/dynamic'
const Main = dynamic(
  () => import('../public/components/main_component'),
  { ssr: false }
)

export default function Index() {
  return <Main />
}
```

これでビルド時エラーは無くなり、動作もしました。

※Mainコンポーネントとして読み込んでいる `main_component.js` は、アプリの起点になるファイルです。


# Elastic UI framework(EUI)の適用

kibana plugin（に限らずElastic関連サービス全般）のUI用に、[Elastic UI framework](https://elastic.github.io/eui/#/) が用意されています。kibana pluginでは[generaterコマンド](https://github.com/elastic/kibana/tree/master/packages/kbn-plugin-generator#quick-start)を実行した時点で、すでに利用できる状態になっていますが、独立用アプリでは自分で用意します。


## install

```
yarn add @elastic/eui
# 他に利用しているcomponentによって追加する
yarn add @elastic/datemath
```

install後は、importで読み込んで利用します。

## import

EUIをスタンドアローンで利用する方法は、[using-eui-in-a-standalone-project](https://github.com/elastic/eui/blob/master/wiki/consuming.md#using-eui-in-a-standalone-project
)に書いてあります。

実際の方法は以下です。

```javascript:pages/_app.js
// darkテーマを選択
import '@elastic/eui/dist/eui_theme_dark.css';
（略）
```

なお、 `pages/_app.js` ファイルは新規で作成しました。このファイルは`Next.js`アプリの[カスタム用](https://nextjs.org/docs/advanced-features/custom-app)に使います。
また、元々使っていたcssも合わせて反映させるよう、_app.js全体を以下のようにしました。

```javascript:pages/_app.js
// darkテーマを選択
import '@elastic/eui/dist/eui_theme_dark.css';
// kibana pluginで利用していたapp.scss
import '../public/app.scss' 
// 上記対応しても反映されなかったstyleは、新規でファイル作成して対応
import '../public/additional.scss'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp
```

これで、kibana plugin側と、サイドバーやタイトル部分を除いたplugin部分のみ、見た目が同じになりました。
※追加したadditional.scssについては[こちら](https://qiita.com/papettoTV/items/518756605c954216470c#%E3%81%9D%E3%81%AE%E4%BB%96tips)


# Elasticsearchとの通信処理

EUI同様Elasticsearch API用モジュールを入れて、通信処理を書くだけです。

## install

```
yarn add @elastic/elasticsearch
```

## 実装ファイルPath

元のkibana plugin側の実装時のAPI URIに合わせるため、ファイルを以下に設置しました。（ルーティング設定でも良いと思います。）

```
pages/api/<plugin name>/<api用root path>
```

例）

```
pages/api/hoge_plugin/elastic_search/search.js
```

# 開発サイクル速度の比較

環境ができたので、実際の速度比較です。
kibana plugin上の開発サイクルで遅かったのは3点

* clientコードのビルド
* ブラウザのリロード
* serverの起動/再起動

これらの速度を比較します。

## 比較

### 実施環境

* 実施マシン
	* MacBook Pro (Retina, 13-inch, Early 2015)
	* 2.7 GHz デュアルコアIntel Core i5
	* 16 GB 1867 MHz DDR3
* 対象アプリ
	* 主にReact.jsで構築された、データ可視化アプリ

### 結果

ちゃんと計測システムを入れたわけではないので、ざっくりです。

|  | kibana plugin環境 | 独立用アプリ環境 |
|---|---|---|
| コードのビルド | 10〜15秒 | 3〜6秒|
| ブラウザのリロード | 10秒 | 1秒（自動リロード）|
| serverの起動 | 270秒 + 90秒（ブラウザ初回表示） | 50秒(自動リロード含む) |

そもそもが遅すぎというのがありますが、 **劇的に向上**できました！
（というかwebアプリってこれぐらいの速度欲しいですよね。。。

秒数にすると多少の差に見えますが、この１サイクルの差を、１日に数十、数百回繰り返すので、その差はとてつもなく大きくなります。


# 課題

サーバサイドでElasticsearchのAPIの結果を返すレスポンス処理が、コールバック内処理なので以下のようなメッセージが出る。

```
API resolved without sending a response for /api/(略), this may result in stalled requests.
```

ただし、クライアントは期待通り動いているので、今の所問題ないとしてます。


# 実施バージョン情報

| name | version |
|---|---|
| kibana |7.6.2|
| next |9.4.4|
|@elastic/eui|26.3.0|
|@elastic/elasticsearch|7.8.0|

  

# その他Tips

* Elasticsearch API ライブラリは、サーバサイドでのみ稼働する
	* clientだけでは、Elasticsearch API接続できません。serverサイドの処理が必要
* kibana本体の環境変数設定ファイル.envを使っている場合はそれをコピー
* style `hidden` が効かなかったので、新規scssファイル追加して対応

# まとめ

* kibana pluginの開発サイクルが遅かった
* 独立したアプリとして切り出した
* 大幅な速度改善できた