JAVAとReact
弊社のサービスは、主にサーバーサイドはJAVAのServlet/JSP、フロントエンドはJQueryで作られている所が多いのですが、色々と新しいアーキテクチャを使えるよう取り組んでいます。
サーバーサイドについては、プラットフォームをdockerコンテナにすることにより、spring-bootを使ったりrubyやGo、node.jsなど他言語でサービスを作ったりしています。 フロントエンドも、全く新しく作るページについてはVue.jsで実装してみたりしているのですが、サービス本体のページに対してもモダンなスタイルのJavaScriptを使えないかと検討しています。
JavaScriptフレームワークの課題
モダンJSを使うにあたり、ReactやVue.js、Angularなどのフレームワーク利用を考えますが、弊社のサービス的には以下の課題があります。
- SEOを強化したいページについて、静的HTMLとしてコンテンツを出力したい
- ファーストビューの表示が遅いと、ユーザーが離脱してしまう
1については、GoogleはJSの解釈も行うと言われていますが、メインコンテンツを動的に出力しようという所までその話を信頼できないと思っています。
今回は、これらの課題に対応しつつJSフレームワークを使うために、Servlet/JSPとJSによるサーバーサイドレンダリングを組み合わせる方法を模索してみました。
JAVAでReactを実行
今回はJSフレームワークとして、Reactで試してみようと思います。 Servlet/JSPとReactの組み合わせ方として、
案1 別プロセスでJavaScriptを実行するnode.jsを動かし、ServletからHTMLを出力する時にnode.jsでのSSR結果をincludeする 案2 JAVAのプロセス内でReactのSSRを実行し、その結果をJSP内に埋め込む
の2通りを考えたのですが、できたら面白そうだなということで、案2を試してみました。
JAVAにはNashornというJSスクリプトエンジンがあるので、それを使ってReactを動かしてみます。
Reactテストアプリ作成
まず、npmでcreate-react-appをインストールし、
npm install -g create-react-app
次にReactのテストアプリを作ります。
$ create-react-app react-test Creating a new React app in /Xxxxx/xxxx/react-test. Installing packages. This might take a couple of minutes. Installing react, react-dom, and react-scripts... ... We suggest that you begin by typing: cd react-test yarn start Happy hacking! $ cd react-test $ npm start Starting the development server...
以上でブラウザが立ち上がり、Reactのサンプルページが出ます。
まず、ReactのサンプルページをSSRしてみます。
babelインストール
JAVAのNashornではJavaScriptは動かせますが、TypeScriptは解釈できません。ReactのサンプルページはTypeScriptで書かれているので、babelでTypeScriptからJavaScriptへコンパイルします。
まずプロジェクトにbabel-cliとbabel-preset-reactをインストールします。
npm install --save-dev babel-cli babel-preset-react
次に、プロジェクトフォルダのpackage.jsonに以下を追加しました。
"scripts": { "start": "react-scripts start", + "build": "babel src -d lib", "test": "react-scripts test", "eject": "react-scripts eject" },
TypeScriptのビルド
これで、以下でビルドできました。
$ npm run build > react-test@0.1.0 build /Xxxxx/xxxx/react-test > babel src -d lib src/App.js -> lib/App.js src/App.test.js -> lib/App.test.js src/index.js -> lib/index.js src/jvm-npm.js -> lib/jvm-npm.js src/serviceWorker.js -> lib/serviceWorker.js
node.jsのrequire
Nashornにはnode.jsのrequire構文は無いので、jvm-npmを使いました。
JAVAの実装
以上の環境を作ってから、JAVA上でReactを実行するコードを以下のようにしました。
public class ReactTest { public static void main(String[] args) throws Exception { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByMimeType("application/javascript"); // requireを使用できるように engine.eval("load('/Xxxxx/xxxx/react-test/src/jvm-npm.js');"); // node.jsのグローバル変数対応 engine.eval("var console = {warn: function() {}};"); engine.eval("var process = {};"); engine.eval("process.env = {};"); engine.eval("process.env.NODE_ENV = 'development';"); engine.eval("var global = {};"); engine.eval("global.window = {};"); // スクリプト読み込み BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("/Xxxxx/xxxx/react-test/lib/server.js"))); StringBuilder sb = new StringBuilder(); String line = reader.readLine(); while (line != null) { sb.append(line).append('\n'); line = reader.readLine(); } String script = sb.toString(); Object ret = engine.eval(script); System.out.println(ret); } }
実行
SSRを実行するために、index.jsを書き換えserver.jsとして以下のコードを作りました。
import 'core-js/es6/map'; import 'core-js/es6/set'; import React from 'react'; import './lib/index.css'; import App from './lib/App'; import { renderToString } from 'react-dom/server' renderToString(<App />);
上記コードを実行すると、以下のHTMLが取得できました。
<div class="App" data-reactroot=""><header class="App-header"><p>Edit <code>src/App.js</code> and save to reload.</p><a class="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">Learn React</a></header></div>
これをServletで実行しJSPに埋め込めば、JAVA上でのReactのSSRを実現できると思います。
ところが…
Reactのサンプルは単純なHTMLの生成だけだったので、別のRESTのAPIをaxiosで呼び出しページに出力するサンプルを作ろうとしたのですが、Nashornでaxiosを使えるようにすることができませんでした…。 axiosをimportすると、どうしてもload moduleのエラーが出てしまいます。
しかも、そもそもNashornはECMAScriptの仕様に追いつけないために非推奨となり、将来的に廃止となるようです。
JavaでJavaScriptを実行する「Nashorn」が非推奨に、ECMAScriptの速い進化に追いつけないと。代替案はGraalVM - Publickey
なので、NashornでのSSRは諦めて、GraalJSで試す(本気か!)か上記案1(確実)でSSRしてみようかなと思います。