Limenius / ReactRenderer

Client and Server-side React rendering from PHP
MIT License
238 stars 37 forks source link

[Feature Request] Add the ability to statically render in to a twig file #19

Closed Garethp closed 5 years ago

Garethp commented 6 years ago

In our development, we've a mixture of entirely react based frontend with some twig templates for static pages. The Twig templates actually end up including static React components for the header and footer, since it shares the header and footer with the front-end. So we've got a static_template.html.twig file that looks like

<!doctype html>
<!-- index.html - used by webpack-dev-server -->
<html>
<head>
    {% block header %}
        <link href="/assets/styles.css" rel="stylesheet" />
    {% endblock %}
</head>
<body>
    {{  react_component('StaticHeader') }}

    {% block content %}
    {% endblock %}

    {{ react_component('StaticFooter') }}
    <script src="/assets/client.js"></script>
</body>
</html>

I was thinking, for speed improvements, it would be nice to have an option to have these actually rendered statically, where it basically rendered the html in to a twig file as a cache (if said file wasn't there) and rendered from that cached twig in the future. That way we can have static components being rendered from react at even less cost than the (admittedly low) cost of hitting up V8JS with a cached context.

Garethp commented 6 years ago

I did some performance testing to see how much it would actually impact anything. From what I could see, here's the results of rendering:

Without Caching

Component Render Time (Milliseconds)
Header 60
Footer 1.5

With Caching

Component Render Time (Milliseconds)
Header 0.7
Footer 0.1

Caching Method

    public function render($componentName, $propsString, $uuid, $registeredStores = array(), $trace)
    {
        $cache = $this->cache;
        $cacheItem = $this->cache->getItem($componentName . '.rendered');
        if ($cacheItem->isHit()) {
            return $cacheItem->get();
        }

        $this->ensurePhpExecJsIsBuilt();
        if ($this->needToSetContext) {
            if ($this->phpExecJs->supportsCache()) {
                $this->phpExecJs->setCache($this->cache);
            }
            $this->phpExecJs->createContext($this->consolePolyfill()."\n".$this->timerPolyfills($trace)."\n".$this->loadServerBundle(), $this->cacheKey);
            $this->needToSetContext = false;
        }
        $result = json_decode($this->phpExecJs->evalJs($this->wrap($componentName, $propsString, $uuid, $registeredStores, $trace)), true);
        if ($result['hasErrors']) {
            $this->logErrors($result['consoleReplayScript']);
            if ($this->failLoud) {
                $this->throwError($result['consoleReplayScript'], $componentName);
            }
        }

        $evaluated = [
            'evaluated' => $result['html'],
            'consoleReplay' => $result['consoleReplayScript'],
            'hasErrors' => $result['hasErrors']
        ];

        $cacheItem->set($evaluated);
        $this->cache->save($cacheItem);
        return $evaluated;
    }

In smaller systems I'm not sure if it's worth it, but for us it's a nice free 50ms and someone else might find that useful

nacmartin commented 5 years ago

Merged!