reactjssilverstripe-4

How to insert (inject?) an existing React app (just a UI without a backend) into a SilverStripe page layout?


My question is: I have been reading the SilverStripe 4 documentation in order to find a way to insert an existing React app (just a UI of nested React components without a backend) into a SilverStripe page layout. Is this possible and how do you make sure SilverStripe has the right dependencies?

My research so far attempting to answer this question: As a traditional React project uses NPM (node package manager) to get all the correct dependencies and Node.js as a server, I am wondering how to approach this as my SilverStripe project runs on an Apache server. I have found this package, do I need to add this to my project? https://www.npmjs.com/package/@silverstripe/webpack-config

The closest I have come across is an article about including React components in a SilverStripe project discusses customising the admin interface of the CMS. I am wanting instead to show React components on a public page of the website. https://docs.silverstripe.org/en/4/developer_guides/customising_the_admin_interface/reactjs_redux_and_graphql/

In the article there is mention of a client-side framework having its own implementation of dependency injection known as Injector. There is no other documentation I can find about this.

I have a PageController class which currently only has requirements (JavaScript and css) relating to SilverStripe. This was made by following the SilverStripe lessons 1 -4.

<?php

namespace {

    use SilverStripe\CMS\Controllers\ContentController;

    use SilverStripe\View\Requirements;

    class PageController extends ContentController
    {

        private static $allowed_actions = [];

        protected function init()
        {
            parent::init();
            // You can include any CSS or JS required by your project here.
            // See: https://docs.silverstripe.org/en/developer_guides/templates/requirements/
                Requirements::css('css/bootstrap.min.css');
                Requirements::css('css/style.css');
                Requirements::javascript('javascript/common/modernizr.js');
                Requirements::javascript('javascript/common/jquery-1.11.1.min.js');
                Requirements::javascript('javascript/common/bootstrap.min.js');
                Requirements::javascript('javascript/common/bootstrap-datepicker.js');
                Requirements::javascript('javascript/common/chosen.min.js');
                Requirements::javascript('javascript/common/bootstrap-checkbox.js');
                Requirements::javascript('javascript/common/nice-scroll.js');
                Requirements::javascript('javascript/common/jquery-browser.js');
                Requirements::javascript('javascript/scripts.js');
        }
    }
}

I also have a ReactPageController class that extends the PageController class. Do I include the requirements (dependencies?) for React here?

<?php
namespace SilverStripe\Lessons;

use PageController;    

class ReactPageController extends PageController 
{
//include another init method here?
}
?>

Then I need to somehow inject the React component in the Layout for the ReactPage.ss ('ReactPage.php' class extends 'Page.php' class). Note: The layout for Page.ss includes a simple navbar, header and footer which ReactPage.ss will inherit.

ReactPage.ss template

<!-- BEGIN CONTENT -->
        <div class="content">
            <div class="container">
                <div class="row">
                    <div class="main col-sm-8"> 
                        <div>
                           <!-- INSERT REACT CONTENT HERE:replace Hello World!-->
                           <h1>Hello World!</h1>
                        </div>                  

                    </div>

                    <div class="sidebar gray col-sm-4">
                        <% if $Menu(2) %>
                        <h3>In this section</h3>
                            <ul class="subnav">  
                                <% loop $Menu(2) %>
                                    <li><a class="$LinkingMode" 
                                     href="$Link">$MenuTitle</a></li>
                                <% end_loop %>
                            </ul>
                        <% end_if %>
                    </div>
                </div>
            </div>
        </div>
        <!-- END CONTENT -->

The result would be a page where instead of hard-coded "Hello World!", there would be a root component with a whole lot of components nested inside. The nested React App would have the same functionality as the standalone React app.

Thank you for your time reading my long question. Any responses will be greatly appreciated. I am just learning React and SilverStripe my question may also be confusing or badly worded so sorry if this is the case. Thanks again :-).


Solution

  • You can inject a frontend application bundle using Requirements::javascript() calls in your controller, or from your template using <% require %> calls. The fundamental concept is "add your JavaScript (and/or CSS) bundles to my frontend view". From there on it's up to your bundle to initiate itself, given that the bundles are available on the page.


    Here's a lightweight example added to a theme:

    # File: ./themes/my-theme/templates/Layout/ReactPage.ss
    <div id="my-react-app">
        If you can see this, your React app hasn't initialised yet
    </div>
    

    If you add a ReactPage in the CMS, then open it on your frontend, you should now see the message that your React app hasn't initialised yet. This works because SilverStripe will look up the inheritance chain of pages to find one that has a template matching its class name. Note that this includes the class's namespace, so if your class has one it'd need to be templates/Foo/Bar/Layout/ReactPage.ss instead for example.

    Now initialise a demo React app:

    You will now need to tell SilverStripe to "expose" your theme assets, which will symlink (by default) them into the public/resources directory, so you can access them from your webroot (public/ folder).

    # File: ./composer.json
    ...
    "extra": {
        "expose": [
            "themes/my-theme/build/static/js/bundle.js"
        ]
    }
    

    Run composer vendor-expose from your root project folder to re-expose all your resources.

    Finally, add your requirements to your SilverStripe template or controller. You can do this via a controller's init() method, or directly in the template:

    # File: ReactPageController.php (note that this may not exist and doesn't need to necessarily)
    use SilverStripe\View\Requirements;
    
    // ...
    
    protected function init()
    {
        parent::init();
    
        Requirements::javascript('themes/my-theme/js/build/static/js/main.ad956de7.chunk.js');
        Requirements::javascript('themes/my-theme/js/build/static/js/2.6efc73d3.chunk.js');
        Requirements::javascript('themes/my-theme/js/build/static/js/runtime~main.a8a9905a.js');
        Requirements::css('themes/my-theme/js/build/static/css/main.5c68d653.chunk.css');
    }
    

    For the purposes of this example I've used the hash names of the files that were created by create-react-app. It'd be easier to integrate in SilverStripe if you have a consistent filename like bundle.js or something.

    You could also do this in your template:

    # File: ./themes/my-theme/templates/Layout/ReactPage.ss
    ...
    <% require javascript('themes/my-theme/js/build/static/js/main.ad956de7.chunk.js') %>
    <% require javascript('themes/my-theme/js/build/static/js/2.6efc73d3.chunk.js') %>
    <% require javascript('themes/my-theme/js/build/static/js/runtime~main.a8a9905a.js') %>
    <% require css('themes/my-theme/js/build/static/css/main.5c68d653.chunk.css') %>
    

    Load up your browser now and you'll see the React app running. It'll be a little broken, but for the sake of this example it's enough to show how to get a React app running on your page.