PHPのV8jsでReactJs-ReduxアプリをSSRからアップデート記事です。専用のユーティリティを使わずにV8JsでSSRをするときの基本的な方法を説明します。
用語
- SSR サーバーサイドレンダリング (V8)
- CSR クライアントサイドレンダリング (webブラウザ)
サーバースクリプト
ReduxのSSRチュートリアルに載っているものとほぼ同じですが、express
サーバーで出力するのではなくPHP
からV8Jsで呼び出して出力します。PHP-JS間のインターフェイスとしてJS側のrender
関数をPHPに公開しています。
import render from './render';
global.render = render;
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import escape from 'escape-html';
import serialize from 'serialize-javascript';
import App from '../containers/App';
import configureStore from '../store/configureStore';
const render = (state, metas) => {
const store = configureStore(preloadedState);
const root = renderToString(
<Provider store={store}>
<App />
</Provider>,
);
return `<!doctype html>
<html>
<head>
<title>${escape(metas.title)}</title>
</head>
<body>
<div id="root">${root}</div>
<script>
window.__PRELOADED_STATE__ = ${serialize(state)}
</script>
<script src="/build/index.bundle.js"></script>
</body>
</html>
`;
};
export default render;
PHPは「render(state)
を実行するJSスクリプト」を文字列にしてexecuteString()
で実行します。
$code = sprintf('
%s
var window = this;
render(%s);',
file_get_contents(__DIR__ . "/ssr.bundle.js"),
json_encode(['hello' =>['name' => 'World']])
);
echo (new V8Js)->executeString($code);
スクリプトの実行でSSRでレンダリングされる${root}
の部分と${serialize(state)}
は以下のようになります。DOMからhtmlが生成され、SSRからCSRに引き継ぐためのJSONが作られます。
<div id="root"><div data-reactroot="" data-reactid="1" data-react-checksum="410796212"><h1 data-reactid="2"><!-- react-text: 3 -->Hello <!-- /react-text --><!-- react-text: 4 -->World<!-- /react-text --></h1><button data-reactid="5">Click</button></div></div>
window.__PRELOADED_STATE__ = {"hello":{"name":"World"}}
クライアントスクリプト
クライアントでは特別なことはありません。SSRでレンダリングされたwindow.__PRELOADED_STATE__
の値からDOMをレンダリングしてドキュメントルートにセットします。
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from '../store/configureStore';
import App from '../containers/App';
const preloadedState = window.__PRELOADED_STATE__;
const store = configureStore(preloadedState);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
SSRは必要
Twitter CardsやOGPのmetaタグを生成する場合にはDOMを作るただけでは不十分でSSRが必要です。
汎用化
この記事の方法では(前回記事やfacebook公式のreact-php-v8jsと違って)PHPからはJSの特定のライブラリに依存することなく、ページ全体をV8Jsでrender
関数を呼び出すことでレンダリングしています。
global.render = (state, metas) => ('<html>..</html>');
state
引数はアプリケーションの初期状態でReduxのSSRでのpreloadState
に使ったりテンプレートエンジンに渡したりする値です。
metas
はSSRのみで使う値で主に、OGPやtitleなどheaderタグ内で使います。
既存のJSアプリがある場合は、このrender
関数の形に適応させたwebpackのエントリーポイントにします。引数の名前は自由ですがPHPから呼び出すためにrender
という名前は固定にします。
PHPとJSのインターフェイスを疎にしたしたことで、ビューの部分の入れ替えは容易になります。
V8Jsのパフォーマンス
PHPでSSRをするには今回紹介しているV8Jsの方法とNode.jsでExpress
サーバーをたてGuzzle
などのhttpクライアントからDOMをWebサービスと取得する方法がありますが、パフォーマンスは圧倒的にV8Jsが有利です。
さらにV8Jsにはスナップショットという機能が用意されていてライブラリを含むアプリケーションコードをbuit inコードとして扱いPHPとV8を高速に連携させることが可能です。詳しくは以下の記事をご覧ください。
開発時にV8Jsを使わない方法
V8Jsの無い環境で開発する場合にはnacmartin/phpexecjsを使用することでSSRが可能です。環境に応じてV8Js
またはNode.Js
のランタイムが選択され実行されます。
use Nacmartin\PhpExecJs\PhpExecJs;
$phpexecjs = new PhpExecJs();
print_r($phpexecjs->evalJs("'red yellow blue'.split(' ')"));
// Array
// (
// [0] => red
// [1] => yellow
// [2] => blue
// )
// READMEより
SSRユーティリティ - Baracoa
koriym/baracoa はこのようなJSアプリケーションをPHPからテンプレートエンジンのように扱うライブラリです。
use Koriym\Baracoa\ExceptionHandler;
use Koriym\Baracoa\Baracoa;
$baracoa = new Baracoa($jsDir, new ExceptionHandler());
$html = $baracoa->render('min', ['name' => 'World']);
echo $html; // Hello World
const render = state => (
`Hello ${state.name}`
)
phpexecjs
が使われてるのでV8Jsの無い環境でも実行可能です。
V8スナップショットの機能も統合されています。
$cache = new FilesystemCache() // PSR-16 simple cache
$baracoa = new CacheBaracoa($appBundleJsPath, new ExceptionHandler(), $cache);
$html = $baracoa->render('min', ['name' => 'World']);
echo $html; // Hello World
UIスケルトン
SSRのJSアプリケーションを開発する時の問題は環境構築やデバックが容易ではないことです。koriym/js-ui-skeletonはJSの開発をすぐに始めらるためのボイラプレートとphpからのテスト実行環境を提供します。サーバーサイドのコードをクライアントで実行してトレースすることもできます。
- airbnbリントで書かれたRedux Reactアプリケーションスケルトン
- Webpack 2 / hot module loader 3など最新のツール
- phpビルトインサーバー + ブラウザシンク
- render()関数
新規プロジェクトとしても、既存のPHPプロジェクトに追加する形でも利用できます。
結論
SSRが必要か不要かには議論があります。しかしCSR/SSRと分けてもそのほとんどのコードは共通のコードです。1つの方向性としてSSRの導入コストやメンテナンスコストを下げてSSRを利用しやすくするのが良いのではないかと思います。JSやPHPのツールを充実させ、手間を減らしてメリットを享受する方法はどうでしょうか。
一方PHPのプロジェクトの場合にSSRをNode.js
で行うかV8Js
で行うかについては議論はどうでしょう。よくメンテナンスされていて、オーバヘッドも少なく高速なV8Js採用でいいのではないでしょうか。