Testing React.js isomorphic rendering with php-v8js and the Symfony Microkernel
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:
- New in Symfony 2.8: Symfony as a Microframework
- PHP microframeworks and the Symfony Framework Micro Kernel
- Symfony Micro Framework
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:
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:
- Server-side React with PHP
- Rendering ReactJS templates server-side
- Introduction to React.js Components and Server Side Rendering in PHP
- reactjs-php-bundle for Symfony2
- Drupal 8 REST API and Isomorphic JavaScript (with React, etc)
- Rendering Riot.js tags in Twig using Node.js