Forest header image

Symfony Finland
Random things on PHP, Symfony and web development

Testing React.js isomorphic rendering with php-v8js and the Symfony Microkernel

React.js is often thought of as as a front end technology. They can, however be rendered on the server side too - using JavaScript. This naturally turns the scale towards Node.js, but PHP with the v8js installation can handle it too.

Let's see how to merge together v8js and the Symfony Microkernel, making it possible to set the initial state of server rendered components using a PHP backend.

First of all, you should have a basic understanding of what React.js components are and how they can be rendered on the server side with PHP. I wrote a short primer on this a few days ago: Introduction to React.js Components and Server Side Rendering in PHP

Once you've wrapped your head around the basic concepts of JSX and so on, let's consider the Symfony Microkernel. The Symfony Framework is traditionally thought of as a large framework that solves many needs, but since Symfony 2.8 there is now a lighter weight option for the AppKernel, called Microkernel:

As rendering JavaScript with PHP is not a new thing, I decided to use the Symfony Micro Kernel for brevity (and learning, of course). All of the examples here are perfectly applicable to projects built with the Symfony Framework, like eZ Platform. The basics apply to Drupal 8, WordPress or pretty much other PHP project for that matter.

V8 and React version woes

The primary requisite for rendering React.js components on the server side using PHP is the v8js extension. It embeds the V8 JavaScript engine to PHP so it can be used for executing JavaScript inline in PHP. Installing v8js may be a bit tricky and it's not readily available in many distributions.

For testing purposes on OS X the Brew php56-v8js package works fine. You might need to install it with the --build-from-source option, which helped me past these fatal errors which I guess had something to do with V8 version conflicts:

#
# Fatal error in ../src/api.cc, line 7040
# Check failed: params.array_buffer_allocator != NULL.
#

==== C stack trace ===============================

 1: V8_Fatal
 2: v8::Isolate::New(v8::Isolate::CreateParams const&)
 3: zim_V8Js___construct(int, _zval_struct*, _zval_struct*
*, _zval_struct*, int)....

Once I got v8js setup and working I moved forward with installing a standard Symfony 3 project with the Symfony Installer. I replaced the kernel with a bare bones Micro Kernel setup and put React and my component to the web/js directory so they can be accessed both by the server and by browsers.

After requiring the react-php-v8js library I did some quick tests to confirm that it was working. I got the following deprecation warnings with version 0.14.3:

Warning: React.renderToString is deprecated. Please use ReactDOMServer.renderToString from require('react-dom/server') instead.

The React.js API is still not stable, so that can happen. To move forward I decided to stay with 0.13 versions, but given the simplicity of ReactJS.php patching it to work with 0.14 releases should be pretty stable.

Once I confirmed that components were being rendered, I moved forward with the implementation of the following application that renders the same React component twice. It gets the server side epoch and sets it as a starting point for a timer.

The application

The component is then rendered twice, once with client side only and once with serverside - with React attaching to it on the client side with a two second delay:

Thumbnail

The source created by the server application is as follows:

<html>
<head>
    <title>Epoch at server</title>
    <script src="/js/react.js"></script>
    <script src="/js/components.js"></script>
</head>
<body>

<h1>Epoch server time</h1>
<h2>Client side only</h2>
<div id="client"></div>
<h2>Server side with a two second delay in client attach</h2>
<div id="server"><div data-reactid=".21ewhgmxi4g" data-react-
checksum="-1278267682"><span data-reactid=".21ewhgmxi4g.0">
Seconds: </span><span data-reactid=".21ewhgmxi4g.1">
1450524178</span></div></div>
<script>

React.render(React.createElement(Timer, {"startTime":
1450524178}), document.getElementById("client"));

setTimeout(function(){
    React.render(React.createElement(Timer, {"startTime"
:1450524178}), document.getElementById("server"));
}, 2000);
</script>
</body>
</html>

You can see that the first component's source is empty and the server rendered component has data attributes tied to it. These are how the Virtual DOM identifies elements. At the end of the script each element is rendered.

The server one is initialised with a two second delay to demonstrate how the user gets the initial view immediately when using server side rendering. After it is initialised it becomes identical in functionality to the client-side only rendered component.

In reality Single Page Applications (SPAs) usually experience this kind of a lag when getting data from backends over slow networks and so on. HTTP/2 server push can be used to alleviate this for first loads of applications, but not completely remove it.

The rendering is done in a minimal kernel using a simple controller like this.

$react_source = file_get_contents(__DIR__.'/../web/js/react.js');
$app_source = file_get_contents(__DIR__.'/../web/js/components.js');

$rjs = new ReactJS($react_source, $app_source);

$rjs->setComponent('Timer', array(
    'startTime' => time()
));

$output = '
<html>
<head>
    <title>Epoch at server</title>
    <script src="/js/react.js"></script>
    <script src="/js/components.js"></script>
</head>
<body>
' . print_r($rjs) . '
<h1>Epoch server time</h1>
<h2>Client side only</h2>
<div id="client"></div>
<h2>Server side with a two second delay in client attach</h2>
<div id="server">' . $rjs->getMarkup() . '</div>
<script>
' . $rjs->getJS("#client") . '
setTimeout(function(){
    ' . $rjs->getJS("#server") . '
}, 2000);
</script>
</body>
</html>
';

The source of the implementation is available on Github: https://github.com/janit/reactfony

Learn more about about PHP and React Rendering:


Written by Jani Tarvainen on Saturday December 19, 2015
Permalink - Tags: react, javascript, v8, v8js, php, symfony

« Profiling slow running PHP processes in production with PHP-FPM's slow log - Symfony Benchmarks: Introduction »