Going Isomorphic with Python and React
Tags: python, react, web, assets
Published: 2017-02-11 20:54:46

---
title: Going Isomorphic with Python and React
published: true
description: Writing Isomorphic Web Applications with React and Python without the need for NodeJS or WebPack.
cover_image: http://i.imgur.com/XUEJsrf.png
tags: python, react, web, assets
---

Isomorphic web applications are applications whose part or all the code is executed both on server and client. The server and the browser share the part or all the code.

One of greatest goals that isomorphic solutions can achieve is improving SEO for Single Page Applications, but so far such features were mostly available for JavaScript developers who could run same code on Browser and NodeJS.

As Browsers only supports JavaScript (unless esoteric technologies are involved) all the widespread libraries and frameworks available to create isomorphic applications expect to run on JavaScript and as a Python developer we were pretty much out of choices.

Being dissatisfied with this situation is what lead me to work on [DukPy](https://github.com/amol-/dukpy), a Python library that aimed at removing the need for NodeJS from my work toolchain (hope it will for other people too).

One of the most widespread isomorphic web frameworks is [React](https://facebook.github.io/react/), which allows to render react components server and attach dynamic behaviours to them once they reach the browser.

A few months ago I wrote a short article on [How to use ES2015 and JSX in Python web applications](https://medium.com/@__amol__/es2015-and-react-in-pure-python-environment-b326dc15012c) without the need for NodeJS or WebAssets.

But it didn't cover how an Isomorphic application could be created, the javascript code of the application was still running on the browser and React Components couldn't provide a pre-rendered version. So I decided to start this article which showcases how to use DukPy and React together to write an isomorphic web application with Python without even installing NodeJS.

If you have not read it yet, make sure to take a look at the ES2015 article as this one will take for granted the concepts explained there are already known.

-------

I'll take for granted that you already have all the required Python packages installed as showcased in the [ES2015 article](https://medium.com/@__amol__/es2015-and-react-in-pure-python-environment-b326dc15012c):


```
$ pip install TurboGears2
$ pip install Kajiki
$ pip install tgext.webassets
$ pip install dukpy
```

Once all the required pieces are in place we can start by making an application that renders a React Component on client side, then we will make the same component render on server and have the browser take over from there.

To do so we will create an **statics/js** directory where we will put all our JavaScript

```
$ mkdir statics
$ mkdir statics/js
```

Make sure to download **react** and **react-dom** into that directory so that they are available to our web app

```
$ cd statics/js
$ curl -O 'https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom.js'
$ curl -O 'https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react.js'
```

Last but not least we need the Component itself, which will be a simple *HelloWorld* component.
Our component will be declared inside a **statics/js/HelloWorld.jsx** file:

```
export class HelloWorld extends React.Component {
  render() {
    return (
      <div className="helloworld">
        Hello {this.props.name}
      </div>
    );
  }
}
```

Then we will create an **app.py** file where we will put the Python code that will start the webserver, create the web application, compile JSX to plain js and serve the index page which will render the component itself:

```
import tg
from tg import AppConfig
from tg import TGController
from tg import expose
import kajiki

page = kajiki.XMLTemplate(u'''<html>
    <head></head>
    <body>
      <div id="isomor"></div>

      <script py:for="m in g.webassets['bundle.js'].urls()"
              src="$m">
      </script>
      <script>
ReactDOM.render(
    React.createElement(HelloWorld.HelloWorld, { name: "World" }),
    document.getElementById('isomor')
);
      </script>
    </body>
</html>
''', mode='html5')


class RootController(TGController):
    @expose()
    def index(self):
        return page(dict(
            g=tg.app_globals
        )).render()


config = AppConfig(minimal=True, root_controller=RootController())
config.renderers = ['kajiki']
config.serve_static = True
config.paths['static_files'] = 'statics'

from webassets.filter import register_filter
from dukpy.webassets import BabelJSX
register_filter(BabelJSX)

import tgext.webassets as wa
wa.plugme(
    config,
    options={
        'babel_modules_loader': 'umd'
    },
    bundles={
        'bundle.js': wa.Bundle(
            'js/react.js',
            'js/react-dom.js',
            wa.Bundle(
                'js/HelloWorld.jsx',
                filters='babeljsx',
            ),
            output='assets/bundle.js'
        )
    }
)

application = config.make_wsgi_app()

from wsgiref.simple_server import make_server
print("Serving on port 8080...")
httpd = make_server('', 8080, application)
httpd.serve_forever()
```

If you try to run the saved **app.py** file with such content and all the dependencies are correctly in place you should see something like:

```
$ python app.py
No handlers could be found for logger "tg.configuration.app_config"
Serving on port 8080...
```

Heading your browser to http://localhost:8080 should greet you with an *“Hello World”*

![HelloWorld](https://cdn-images-1.medium.com/max/800/1*SD4GWZEbEW7gsmejEndYYg.png)

If anything is unclear, make sure you start from the previous [React in Pure Python](https://medium.com/@__amol__/es2015-and-react-in-pure-python-environment-b326dc15012c) post, as that will explain step by step what happened so far.

------

Now that our app is in place we can start introducing server side rendering for React.

This requires one additional component we will have to download into our **statics/js** directory, the **react-dom-server** library which allows server side rendering of React

```
$ cd statics/js
$ curl -O 'https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom-server.js'
```

Then we are going to render our component into our Python web application, to do so we are going to rely on DukPy for the actual rendering and WebAssets for providing the required dependencies

```
import json
from dukpy import JSInterpreter, jsx_compile
from markupsafe import Markup


class ReactRenderer(object):
    def __init__(self, jspath):
        self.jspath = jspath
        self.jsi = JSInterpreter()
        self.jsi.loader.register_path(self.jspath)
        self.components = {}
        self.initialized = False

    def _init(self):
        if self.initialized:
            return

        bundle_js = tg.app_globals.webassets['bundle.js']
        self.jsi.evaljs(
            [f.data() for f in bundle_js.build()] +
            ["var ReactDOM = require('react-dom-server');"]
        )
        self.initialized = True

    def render(self, component, **kwargs):
        self._init()
        code = "ReactDOM.renderToString(React.createElement({component}, {args}), null);".format(component=component, args=json.dumps(kwargs))
        return Markup(self.jsi.evaljs(code))
```

The *ReactRenderer* is a convenience class that will create a DukPy interpreter with **React** and our **HelloWorld** component preloaded (through the **bundle.js** WebAssets bundle we already declared) and **react-dom-server** loaded through **require**

In fact the class consists of a single **render()** method which will initialise the interpreter (if it's not already initialised) and will then render the specified React Component. So we can use this class to render any Component that was available into our **bundle.js** including the HelloWorld one.

Only part left is we need to create it and provide it to our **index()** action so that it can use it to render the component. For convenience and as usually I'll need the ReactRenderer object available everywhere I'll make it available into the configuration of my application

```
import os
config.react_renderer = ReactRenderer(
   os.path.join(os.path.dirname(__file__), 'statics', 'js')
)
```

Make sure you add this line before creating the TurboGears application (so before `make_wsgi_app`). The argument provided to ReactRenderer is the path where it can find any additional javascript module that will be loaded through **require**, in this case as we downloaded *react-dom-server* in **statics/js** that's the specified path.
Now that our ReactRenderer is in place we can edit our **index** action and provide the react renderer to our HTML template

```
class RootController(TGController):
    @expose()
    def index(self):
        return page(dict(
            render_react=tg.config['react_renderer'].render,
            g=tg.app_globals
        )).render()
```

If you properly added the `render_react` value to the ones the controller action provides to the `page` template we can now change the template itself to render the component.

If you remember we previously had an empty `isomor` div

```
<div id="isomor"></div>
```

that div acted only as a target for our `ReactDOM.render` call which rendered the component and placed it into the div.

This was pretty clear through the fact that our page when loaded was empty for a moment and then the content appeared a little later when React was able to render it.

What we are going to do is replacing that empty div with one with the component pre-rendered inside:

```
<div id="isomor">${render_react('HelloWorld.HelloWorld', name='World')}</div>
```

The `render_react` callable is in fact the `ReactRender.render` method we provided from the action. If you remember the first argument is the Component that should be rendered (in this case `HelloWorld` from the `HelloWorld` module) and any additional keyword argument is passed as a property of the component. In this case we are providing the `name=World` property (same as we did in the `React.createElement` call).

Note that it is really important that any property provided to the Component when rendering it from python matches those provided to the *React.createElement* call in JS or React will complain and will replace the div content instead of reusing it (same will happen if you wrongly put empty spaces before or after the rendered component).

------

If everything worked as expected, the slight moment where your page was empty should have disappeared. The component is now server pre-rendered by Python and React will just kick in on the browser continuing where python left from.

Congratulations! We achieved our first Isomorphic Application in pure Python!Â :D

The gist for the code used in this article is available on GitHub: https://gist.github.com/amol-/4563c7dc27c94d8ea58fabacb4cd71c6

*This article was originally published on [https://medium.com/@__amol__](https://medium.com/@__amol__)*