JavaScript SSR
ビューのレンダリングをTwigなどのPHPのテンプレートエンジンが行う代わりに、サーバーサイドのJavaScriptが実行します。PHP側は認証・認可・初期状態・APIの提供を行い、JavaScriptがUIをレンダリングします。既存のプロジェクトの構造で、アトリビュートが付与されたリソースのみに適用されるため、導入が容易です。
背景と適用場面
このモジュールは、PHPアプリケーション内でJavaScriptによるサーバーサイドレンダリング(SSR)を実現するために開発されました。
現在では、Next.js、Nuxt、Remixなど、JavaScriptエコシステム側でSSRを完結させるフレームワークが成熟しています。新規プロジェクトでJavaScript中心のUIを構築する場合、これらのフレームワークが第一選択となるでしょう。
本モジュールが適しているのは以下のようなケースです:
- 大部分のページは既存のテンプレートエンジンでレンダリングし、高度なインタラクティブ性が求められる一部のページのみJS UIを使いたい
- 既存のBEAR.Sundayプロジェクトに、特定のページのみReactやVue.jsのUIを追加したい
- フロントエンドとバックエンドを分離せず、単一のPHPアプリケーションとして運用したい
- PHPチームとJSチームが
state/metasを契約として並行開発したい
#[Ssr]アトリビュートを付与したリソースのみがJS UIでレンダリングされるため、従来のテンプレートエンジンとの共存が容易です。
前提条件
注:V8Jsがインストールされていない場合、Node.jsでJavaScriptが実行されます。
JavaScript
インストール
プロジェクトにbear/ssr-moduleをインストールします:
# 新規プロジェクトの場合
# composer create-project bear/skeleton MyVendor.MyProject; cd MyVendor.MyProject
composer require bear/ssr-module
UIスケルトンアプリケーションkoriym/js-ui-skeletonをインストールします:
composer require koriym/js-ui-skeleton 1.x-dev
cp -r vendor/koriym/js-ui-skeleton/ui .
cp -r vendor/koriym/js-ui-skeleton/package.json .
npm install
UIアプリケーションの実行
まずはデモアプリケーションを動かしてみましょう。表示されたWebページからレンダリング方法を選択して、JavaScriptアプリケーションを実行します:
npm run ui
このアプリケーションの入力はui/dev/config/の設定ファイルで行います:
<?php
$app = 'index'; // index.bundle.jsを指定
$state = [ // アプリケーションステート
'hello' => ['name' => 'World']
];
$metas = [ // SSRでのみ必要な値
'title' => 'page-title'
];
return [$app, $state, $metas];
設定ファイルをコピーして、入力値を変更してみましょう:
cp ui/dev/config/index.php ui/dev/config/myapp.php
ブラウザをリロードして新しい設定を試します。このように、JavaScriptや本体のPHPアプリケーションを変更せずに、UIのデータを変更して動作を確認することができます。
このセクションで編集したPHPの設定ファイルは、npm run uiで実行する時のみに使用されます。PHP側が必要とするのは、バンドルされて出力されたJavaScriptファイルのみです。
UIアプリケーションの作成
PHPから渡された引数を使ってレンダリングした文字列を返すrender関数を作成します:
const render = (state, metas) => (
__AWESOME_UI__ // SSR対応のライブラリやJSのテンプレートエンジンを使って文字列を返す
);
stateはドキュメントルートに必要な値、metasはそれ以外の値(例えば<head>で使う値など)です。renderという関数名は固定です。
ここでは名前を受け取って挨拶を返す関数を作成します:
const render = state => (
`Hello ${state.name}`
);
ui/src/page/hello/server.jsとして保存して、webpackのエントリーポイントをui/entry.jsに登録します:
module.exports = {
hello: 'src/page/hello/server'
};
これでhello.bundle.jsというバンドルされたファイルが出力されるようになりました。
このhelloアプリケーションをテスト実行するためのファイルをui/dev/config/myapp.phpに作成します:
<?php
$app = 'hello';
$state = ['name' => 'World'];
$metas = [];
return [$app, $state, $metas];
以上です!ブラウザをリロードして試してください。
render関数内では、ReactやVue.jsなどのUIフレームワークを使ってリッチなUIを作成できます。
通常のアプリケーションでは、依存を最小限にするためにserver.jsエントリーファイルは以下のようにrenderモジュールを読み込むようにします:
import render from './render';
global.render = render;
ここまでPHP側の作業はありません。SSRのアプリケーション開発は、PHP開発と独立して行うことができます。
PHP
モジュールインストール
AppModuleにSsrModuleモジュールをインストールします:
<?php
use BEAR\SsrModule\SsrModule;
class AppModule extends AbstractAppModule
{
protected function configure()
{
// ...
$build = dirname(__DIR__, 2) . '/var/www/build';
$this->install(new SsrModule($build));
}
}
$buildフォルダはJavaScriptファイルがあるディレクトリです(ui/ui.config.jsで指定するwebpackの出力先)。
#[Ssr]アトリビュート
リソースをSSRするメソッドに#[Ssr]アトリビュートを付与します。appにJavaScriptアプリケーション名を指定する必要があります:
<?php
namespace MyVendor\MyRedux\Resource\Page;
use BEAR\Resource\ResourceObject;
use BEAR\SsrModule\Annotation\Ssr;
class Index extends ResourceObject
{
#[Ssr(app: 'index_ssr')]
public function onGet(string $name = 'BEAR.Sunday'): static
{
$this->body = [
'hello' => ['name' => $name]
];
return $this;
}
}
$this->bodyがrender関数の第1引数として渡されます。
CSRとSSRの値を区別して渡したい場合は、stateとmetasでbodyのキーを指定します:
#[Ssr(app: 'index_ssr', state: ['name', 'age'], metas: ['title'])]
public function onGet(): static
{
$this->body = [
'name' => 'World',
'age' => 4.6E8,
'title' => 'Age of the World'
];
return $this;
}
実際にstateとmetasをどのように渡してSSRを実現するかは、ui/src/page/index/serverのサンプルアプリケーションをご覧ください。
影響を受けるのはアトリビュートを付与したメソッドだけで、APIやHTMLのレンダリングの設定はそのままです。
PHPアプリケーションの実行設定
ui/ui.config.jsを編集して、publicにWeb公開ディレクトリを、buildにwebpackのビルド先を指定します。buildはSsrModuleのインストール時に指定したディレクトリと同じにします:
const path = require('path');
module.exports = {
public: path.join(__dirname, '../var/www'),
build: path.join(__dirname, '../var/www/build')
};
PHPアプリケーションの実行
npm run dev
ライブアップデートで実行します。PHPファイルの変更があれば自動でリロードされ、Reactのコンポーネントに変更があれば、リロードなしでコンポーネントがアップデートされます。
ライブアップデートなしで実行する場合はnpm run startを実行します。
lintやtestなどの他のコマンドについては、コマンドをご覧ください。
パフォーマンス
V8のスナップショットをAPCuに保存する機能を使って、パフォーマンスの向上が可能です。ProdModuleでApcSsrModuleをインストールしてください。V8Jsが必要です:
$bundleSrcBasePath = dirname(__DIR__, 2) . '/var/www/build';
$this->install(new ApcSsrModule($bundleSrcBasePath));
$bundleSrcBasePathはJavaScriptバンドルファイルがあるディレクトリのパスです。