Now in Eggmatters:

Creating a REACT stack in the PMF

A guide on building a React stack on top of the PMF


Overview

I wrote this post for 2 reasons:

  • I wanted to build out a Content Management System (CMS) for the PMF but, I wanted it to be seperated from the overall development of the PMF.
  • There is a wide variety of "Getting Started with React" posts and articles But none explain really what the context is. This post will try to alleviate that.

This article describes not only how to build a react stack on the PMF but basically, on top of any framework.

When I looked into what it would take to shoehorn a React stack on top of the PMF, it turned out to be not much. Since the PMF attempts to make as few assumptions as possible about assets and 3rd party software, React fits right in. The general spirit motivating this project is more from a hacker mentality of seeing if it works, making it work, and seeing what will happen

Getting Started

We will be creating a node / npm project within the PMF to build our stack. The React stack will be an isomorphic, or Single Page App (SPA) that lives (for now) publicly in the PMF. The PMF will serve static content. Essentially, the HTML structure of the SPA and surrounding assets. As the SPA grows, the PMF will serve double duty as API to the web app.

Source code is available on github: https://github.com/eggmatters/cms_react

In your PMF project, navigate to html/assets and drop a node project there:


$ npm init

And answer all of the questions. If you've downloaded the source repo, you can skip this step and modify the existing package.json to suit your needs. Either way, we will need npm packages to build our React stack.

Webpack
Webpack "compiles" all of your react components and creates a single javascript file that gets loaded on request. Webpack consists of two packages:

        webpack
        webpack-cli
    
Babel
Babel ensures that features in ECMA-6 are supported in ECMA-5 only browsers. Additionally, babel also ensures that React specific files are "transpiled" into ECMA-5 friendly formats.
Babel ships as a collection of packages, so we need to provide only our react specific packages:

        babel-loader
        babel-preset-env
        babel-preset-react
        css-loader
        style-loader
        //Development only packages:
        babel-cli
        babel-core
    
React
Finally the actual React packages. This allows us to write React components as well as React JSX. JSX will be how we template HTML. There are two npm packages we need:

    react
    react-dom
    
React is the actual React framework. The react-dom package serves 2 purposes, it allows JSX to turn into HTML. It also allows DOM manipulations to occur in context of React and not the actual DOM, speeding up your SPA's.
So, run the following npm commands to install the above setup:

$npm install --save webpack /
        webpack-cli
        save babel-loader /
        babel-preset-env /
        babel-preset-react /
        css-loader /
        react /
        react-dom
And:

$npm install --save-dev babel-cli babel-core

You are all set to build a React application. But before we do that, we need to ensure it gets to the browser.

Configuring WebPack & Babel

Under html/assets drop the following directories: html/assets/src & html/assets/dist. in addition to your package.json file.

Add a file called .bablerc to this directory and add the following:


{
  "presets": ["env", "react"]
}

These are two plugins, which babel calls "presets" which define specific behaviors for babel. env targets, or can be configured to target specific browsers. react, allow babel to compile react node.js.

A little more engaged is webpack. Create a top level file: webpack.config.js Webpack looks for this file when it runs. Here is what you need in the file:


const path = require("path");
const webpack = require("webpack");
const bundlePath = path.resolve(__dirname, "dist/");

module.exports = {
  mode: "development",
  entry: "./src/index.js",
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/,
        loader: 'babel-loader',
        options: { presets: ['env'] }
      },
      {
        test: /\.css$/,
        use: [ 'style-loader', 'css-loader' ]
      }
    ]
  },
  resolve: { extensions: ['*', '.js', '.jsx'] },
  output: {
    publicPath: bundlePath,
    filename: "bundle.js"
  },
  plugins: [ new webpack.HotModuleReplacementPlugin() ]
};

Hopefully, you can see how it all comes together. This is just a standard, straight up Node module. Just like in the PMF, we chose to use Native PHP traits to supply configuration options -- as oppsed to yaml or json -- we're just exporting an object as a node module.

mode
Either "development" or "production"
entry
Relative path to where to begin the application.
module.rules
We've set two rules. One for js & jsx files and another for css. We won't add css just yet as we'd have to rewrite bootstrap css as javascript objects. Maybe later. Anyways, we are going to transpile using babel, with our predefined babel presets.
resolve
I think that one is pretty obvious
output
Where we want to put our pipelined webpacked asset. This is the one file that represents our React stack. We will be writing a file: assets/html/cms_react/dist/bundle.js or, rather, webpack will do that.

In your package.json file, add a shortcut to run webpack:


"scripts": {
   "test": "mocha",
   "build": "webpack --config webpack.config.js"
 }
This allows us to run the command: npm run build to prepare our app. We don't have anything to build just yet. We'll need to provide a html layout that can reference the bundle.js

Routing your SPA with PMF

We're jumping the gun a bit here and we're going to dive in with PMF routing. That article is next.

Set up a controller

The controller / endpoint itself is arbitrary. For this example, I'm setting up the SPA to serve my CMS so, I'm going to create an Index controller under a admin/ namespace.

Since this is a single page app, let's discuss the responsibilities of both React & the PMF:

All requests to the PMF will be API requests serving JSON via Ajax. The PMF is done serving html.
The React app will now manage all requirements of rendering data, as well as view specific business logic.
The PMF API's shall be as close to CRUD as possible.

That's basically some standard guidelines. Let's create our index controller. Under app/controllers create an admin directory.

Now push the following file to it: IndexController.php


namespace app\controllers\admin;

use core\ControllerBase;
use core\View;
use core\CoreApp;

class IndexController extends ControllerBase {
   
   public function index() {
      $viewPath = CoreApp::appDir()
        . DIRECTORY_SEPARATOR . 'views'
        . DIRECTORY_SEPARATOR . 'layouts'
        . DIRECTORY_SEPARATOR . 'cms' 
        . DIRECTORY_SEPARATOR . 'layout.php';
      View::render($viewPath, []);
   }

}
So rather than rely on MVC conventions to render the single page app, I created a cms layout and will just be rendering that. One of the reasons I did this was because I already have an "index" view. The render method in CoreController doesn't expand namespaces just yet so, until I fix that, I'll have to render directly from the View utility. Also, I'm also rendering a layout, cms. All I did above was include the layout.php file located at: app/views/layouts/cms

Create the following layout directory: app/views/layouts/cms

However you want to structure your layout is up to you. Just make sure it has a reference to your bundle.js. Also, it is important that you tag an entry point with the div id="root" identifier. This tells react where to put stuff.

   <div id="root"></div>
   <noscript>
   You need to enable JavaScript to run this app.
   </noscript>
   <script src="/assets/cms_react/dist/bundle.js"></script>

If you run the app, you'll see whatever you put in the layout. We have a React Stack, just no actual React. Additionally, Developer tools / Firebug will greet you with a fat console error about not finding bundle.js. So, on to the last bit.

A React Hello World (but instead I wrote "GOT HERE")

So now, we need to serve content. We also need to tell a client's browser what to do with that content. This involves writing some React and configuring webpack to compile our missing bundle.js file.

Under your src/ directory, create two files: App.js & index.js We configured index.js to be the entry point of the app. It should look like this:


import React from "react";
import ReactDOM from "react-dom";
import App from "./App.js";

ReactDOM.render(
  <App />,
  document.getElementById("root")
);

This script imports the App.js file which (for a hello world type of thing) simply:


import React, { Component} from "react";

class App extends Component {
   render() {
      return(
         <div className="App">
            <h1> GOT HERE </h1>
         </div>
      );
   }
}

export default App;

The index.js gets the ball rolling. It brings in React JSX from the App file, and then calls plain ol' javascript to stuff the parsed JSX (resultant html) into the div tag you created with the "root" id.

The App, is JSX that says "GOT HERE." You are ready to go reacting.

All that's left is to compile and provide our bundle.js file. from the cms_react directory, or where you put your webpack.config.js file, run the following:


$npm run build

Remember, this is the alias we set up in the package.json to run webpack with our config script.

Ok, we are ready to pull the whole thing together. If you set up your route like I did, then just navigate to http(s)://<your app here>/admin and see the results.

By now, you should be able to get a solid gist of how the stack flows. Webpack dictates all of the dependencies and rewrites them into a single js asset that gets loaded. This way, that asset will be cached and we've offloaded a huge chunk of application logic and all of our rendering to the client. Free clock ticks!

It's just a matter of connecting the dots. A html document is loaded with a resource to our application. The first thing our application does is compiles contents and inserts it into the dom at our div with the "root" id.

Conclusion

Congratulations, you've just bootstrapped React to PHP. If you read between the lines here, the only role the PMF has is to provide an answer to an http request. If you want to do things with the PMF as well as do things with React, then great! If not, then just serve from node Express or something.

Also, I wouldn't recommend this layout. Your application could be susceptible to cross-site scripting without some careful hackery. I will update when I have a more secure configuration.

Happy Trails!