Spring BootとGraalJSでVue.jsをSSRする試み

## 概要
GraalJSを触りがてら、SSRをやってみます。
JavaでもSSRしたいよって人の参考になれば幸いです。

## GraalJSについて
[GraalVM](https://www.graalvm.org/)はJVMやJavaScript、Python、Rubyなどが動かせるVMです。
[GraalJS](https://github.com/graalvm/graaljs)はGraalVM環境で動作させることでパフォーマンスが最適化されるとのこと（公式より）ですが、Javaで書かれているため通常のJVMでも動作が可能となっています。
今回はJVMでGraalJSを使用しSSRを試みたいと思います。


## 環境
- macOS Mojave
- OpenJDK　11
- Node.js v10.16.3
- Yarn v1.17.3

## JSファイル
JS側の設定等詳しく述べませんが、[VueSSRガイド](https://ssr.vuejs.org/ja/)に沿って、Server側とClient側のJSファイルをwebpackで用意します。

```javascript:webpack.config.js
    node: {
        child_process: 'empty',
        fs: 'empty',
        net: 'empty',
        tls: 'empty',
    },
```
サーバー側で動かすために、上記を追加します。

Javaアプリの中で、vue-server-rendererとBundleファイルを読み込みHTMLを描画させます。

```javascript:entry-server.js
import { createApp } from './app'

const { app, router, store } = createApp()

router.push(route)

router.onReady(() => {
    renderVueComponentToString(app, (err, res) => {
        rendered = String(res)
    })
})
```
renderVueComponentToStringで取得されたHTMLをグローバル変数renderedに入れ込むように処理を書いています。

## ScriptEngine
JavaのScriptEngineでGraalJSを使用するため、ライブラリを追加します。

```xml:pom.xml
		<!-- https://mvnrepository.com/artifact/org.graalvm.js/js -->
		<dependency>
			<groupId>org.graalvm.js</groupId>
			<artifactId>js</artifactId>
			<version>19.2.0</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.graalvm.js/js-scriptengine -->
		<dependency>
			<groupId>org.graalvm.js</groupId>
			<artifactId>js-scriptengine</artifactId>
			<version>19.2.0</version>
		</dependency>
```

```java:Renderer.java
    public String render(String route) throws ScriptException, IOException {

        ScriptEngine engine = getEngine();
        ScriptContext context = getContext();
        Bindings engineScope = engineSetting(engine, context);
        engineScope.put("rendered", null);    // グローバル変数宣言
		engineScope.put("route", route);      // グローバル変数宣言
        engine.eval(read("static/js/server.js"), context);
        
		return context.getAttribute("rendered").toString();　// rendered変数を取得しString型へ
	}

    private Bindings engineSetting(ScriptEngine engine, ScriptContext context) throws ScriptException, IOException {
        context.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
        Bindings engineScope = context.getBindings(ScriptContext.ENGINE_SCOPE);

        engine.setContext(context);

        engine.eval(
                "var process = { env: { VUE_ENV: 'server', NODE_ENV: 'production' }}; this.global = { process: process };",
                context);
        loadFiles(engine, context);　　// vue-server-rendererの読み込み

        return engineScope;
    }
```

Bindingsクラスのputメソッドでグローバル変数の宣言を行い、ScriptContextクラスのgetAttributeメソッドで値の取得ができます。

```java:Renderer.java
    private ScriptEngine getEngine() {
        return new ScriptEngineManager().getEngineByName("graal.js");
    }

    private ScriptContext getContext() {
        return new SimpleScriptContext();
    }
```
VueSSRガイドにあるようにJS側がシングルトンになることを避けるため、ScriptEngineはリクエストがあるたびに新しいインスタンスを作成するように処理しています。


## 描画

取得したHTMLをThymeleafで描画します。

```html:index.html
<body th:utext="${rendered}">
</body>
```
Vue側では描画に使用するエレメントIDを含めて描画するようにしてください。


## 初期データ取得
初期描画時のデータはHTMLファイルにJSONを書き出すことで、取得します。
これはthymeleafで実現します。

```html:index.html
    <script th:inline="javascript">
      /*<![CDATA[*/	    
      window.__INITIAL_STATE__ = /*[[${word}]]*/ {};
      /*]]>*/
    </script>
```


githubに[サンプルプロジェクト](https://github.com/SakaiBorder/java-vue-ssr)をあげていますので、参考になれば幸いです。


## 感想
SSRはNuxtとかを使ってやったほうが簡単ですね。

## 参考
[Vue.js サーバサイドレンダリングガイド](https://ssr.vuejs.org/ja/)
[terwer/spring-vue-ssr](https://github.com/terwer/spring-vue-ssr)