Server-side React with PHP

September 13th, 2013. Tagged: JavaScript, php, react

Problem: Build web UIs
Solution: React
Problem: UI built in JS is anti-SEO (assuming search engines are still noscript) and bad for perceived performance (blank page till JS arrives)
Solution: React page to render the first view
Problem: Can't host node.js apps / I have tons of PHP code
Solution: Use PHP then!

This post is an initial hack to have React components render server-side in PHP.

Previously...

So you know about React and how to build your own components. And you know you can run JavaScript inside PHP scripts, thanks to v8js. So nothing can stop you from rendering React components on the server side in PHP. Which means you send the first view from the server and then continue from there.

Step by step (oh baby!)

  1. Get the latest React build, unzip to a react/ sub-directory of where your PHP scripts live on the server
  2. Create your own components, and put them in react/build/ too for simplicity. I'll just have one Table component from the earlier blog post
  3. Create test.php that concatenates JS code consisting of: stubs, react, custom components. Fetch (or fake) data somehow (this where you PHP code you've been slaving away for the past 3 years shines). Render the custom component with the PHP-fetched data.
  4. Load http://localhost/test.php
  5. Profit!

How (A-haw haw haw haw)

First, the custom component goes into react/build/table.js:

var Table = React.createClass({
  render: function () {
    return (
      React.DOM.table(null, React.DOM.tbody(null,
        this.props.data.map(function (row) {
          return (
            React.DOM.tr(null, 
              row.map(function (cell) {
                return React.DOM.td(null, cell);
              })));
        }))));
  }});

Alternatively the more readable version goes to react/src/test.js and you transform it to the build version:

var Table = React.createClass({
  render: function () {
    return (
      <table><tbody>
        {this.props.data.map(function(row) {
          return (
            <tr>
              {row.map(function(cell) {
                return <td>{cell}</td>;
              })}
            </tr>);
        })}
      </tbody></table>
    );
  }
});

Now, let's see about this test.php. Starting:

 
<?php
$v8 = new V8Js();

Hm, easy enough. Now let's start pushing some JS to an array to concatenate later.

$react = array();

Concatenating code is ugly, but here in PHP we have to pass JS code to V8 as a string. Surely the following code can be cleaned up a bit by using external files, but for a quick hack it's just "perfect" :).

// stubs, react
$react[] = "var console = {warn: function(){}, error: print}";
$react[] = "var global = {}";
$react[] = file_get_contents('react/build/react.js');
$react[] = "var React = global.React";

React uses console.warn() and console.error() which do not exist in V8. But there's print() in V8. So warnings will be silenced and errors will be printed on the page. We can be more creating here, e.g. print server-side JS errors in client side JS console but hey, this is a proof of concept.

The global jazz is a React bug IMO because at the top of the react.js file there's a bootstrapping code that goes like: window ? window.React : global.React. In V8 there's no window nor global, so hence the workaround.

Load custom react components:

// my custom components
$react[] = file_get_contents('react/build/table.js');

Now the "brains" of the app. We must fetch data somehow. This is where your legacy PHP can do its thing. We don't care about this as long as we end up with an array of data to put in a table

// my application
$data = array( // database, web services, whatevers
    array(1, 2, 3),
    array(4, 5, 6),
    array(7, 8, 9));

Now for the magic: instead of rendering a react component in a DOM node, you can render it to a string. This is an async operation, so you need to pass a callback. V8's print() is the most appropriate callback. It will simply pass the output to PHP's print()/echo

$react[] = sprintf(
  "React.renderComponentToString(Table({data: %s}), print)",
  json_encode($data));

That's enough JavaScript!

// concat all JS
$react = implode(";\n", $react);

Run the JavaScript:

try {
  $v8->executeString($react);
} catch (V8JsException $e) {
  // blow up spectacularly
  echo "<pre>"; var_dump($e);
}

Boom!

phpreact

todo

  • Marry the server-side generated code with React on the client side to handle events and reactive updates (in my example I don't even load client side React)
  • Strip the events part of React as it's not needed server-side

Comments? Find me on BlueSky, Mastodon, LinkedIn, Threads, Twitter