Implementing a Mini-React-Redux Framework on a Django Page

Introduction

I have built several production web applications using React and Redux and generally have had an excellent experience with those technologies.  One of React’s greatest assets IMO is it’s ability to integrate into all kinds of stacks and setups but still play nice with the other kids.  That was something that impressed me back in Spring 2014 when I first used React.  We got React running in the jQuery spaghetti code of a massive, legacy Ruby on Rails application with incredibly little effort and huge productivity benefits to the team.  Redux is also incredible for the amount of good it does you with so little code.

There are lot’s of blogs and tutorials on how to build a full single-page application (SPA) complete with client-side routing, persistent state, and even server-side rendering to boost that time-to-interactivity metric.  What if I don’t need that?  What if I already have a site built using an “old-school” server-side framework like Ruby on Rails or Django, but I have one specific page that should be highly interactive and need something more robust than simple jQuery?  React and Redux could still be hugely beneficial, but how do I do it without (a) getting bogged down in boilerplate or (b) over-engineering the solution?

Mini React-Redux Framework to the rescue!

Ready, Set, Go!

Let’s make the skeleton of a super, tiny JavaScript framework that can fit our use case for a Django website.

Here are the steps we’ll follow:

  1. Install our client dependencies
  2. Setup Webpack with Django
  3. Implement the Mini React-Redux Framework

Install our client dependencies

The following are the NPM dependencies I am relying on:

{
  "dependencies": {
    "babel-core": "~6.3.26",
    "babel-loader": "~6.2.0",
    "babel-preset-es2015": "~6.3.13",
    "babel-preset-react": "~6.16.0",
    "react": "~15.4.2",
    "react-dom": "~15.4.2",
    "redux": "~3.6.0",
    "redux-logger": "~2.7.4",
    "redux-thunk": "~2.2.0",
    "webpack": "~1.13.2",
    "webpack-bundle-tracker": "0.0.93"
  }
}

Include these dependencies in your package.json and run npm install.

Setup Webpack with Django

For this step, we are going to use the django-webpack-loader tool to give us the power to load Webpack bundles onto a templated page.  The setup is very simple if you have a vanilla Django application; just follow the loader tutorial.  If you are using Django-Mako-Plus add-on, supplement the regular loader tutorial with my own little tutorial.  I will give a high-level overview.

We need a webpack.config.js. Here is a pre-v2 webpack config file that we can use:

var path = require('path');
var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');

module.exports = {
  context: __dirname,
  entry: {
    myapp: './client/myapp/index',
  },
  output: {
      // IMPORTANT: Need to match this up with settings STATICFILES_DIRS and WEBPACK_LOADER
      path: path.resolve('./static/bundles/'),
      filename: "[name]-[hash].js",
      // OPTIONAL: In this setup, it can be helpful to namespace the exported files
      library: 'MyCompanyApp',
      libraryTarget: 'var'
  },

  plugins: [
    // IMPORTANT: django-webpack-loader needs to know where this file is
    new BundleTracker({filename: './webpack-stats.json'})
  ],

  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          plugins: ['transform-runtime'],
          presets: ['es2015', 'react', 'stage-2'],
          cacheDirectory: true
        }
      }
    ]
  }
}

You will need to throw in some settings for the Django webpack loader plugin so it knows where to find certain key files. Here are some simple defaults:

INSTALLED_APPS = (
    # ...
    'webpack_loader'
)

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)

WEBPACK_LOADER = {
    'DEFAULT': {
        # Lets not cache for dev builds; you can enable for prod builds
        'CACHE': False,
        # NOTE: where, inside the staticfiles, are the output? Must end with slash
        'BUNDLE_DIR_NAME': 'bundles/',
        # NOTE: STATS_FILE is the path to the file that the BundleTracker webpack plugin is writing.
        'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
        'POLL_INTERVAL': 0.1,
        'TIMEOUT': None,
        'IGNORE': ['.+\.hot-update.js', '.+\.map']
    }
}

That should do for now.

Implement the Mini React-Redux Framework

Dan Abramov is a smart guy. He wrote Redux. He encourages devs not to use Redux until you know that you need it; just use Props and State. I strongly support that! However, in this post I want to demonstrate the more complicated case of using Redux including with some middlewares just to show how simple it is. I encourage you to pair this example down to only what you need.

Here is the source I came up with for our mini framework:

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import createLogger from 'redux-logger'
import MyComponent from './components/MyComponent'

/**
 * Redux Reducer.
 * @params:
 *  - state: the previous state of the store
 *  - action: an object describing how the state should change
 * @returns:
 *  - state: a new state after apply appropriate changes
 */
const rootReducer = (state = { clicks: 0 }, action) => {
  // ... change state based on action
  return state
}

/**
 * Redux Store object with three functions you should care about:
 *  - getState(): returns the current state of the store
 *  - dispatch(action): calls the reducer with a given action
 *  - subscribe(): called after a reducer runs
 *
 * The store has two optional middlewares to showcase how you would add them:
 *  - redux-thunk: allows `store.dispatch()` to receive a thunk (function) or an object
 *                 See http://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559
 *  - redux-logger: logs out redux store changes to the console. Only in dev.
 */
const middlewares = process.env.NODE_ENV === 'production'
    ? applyMiddleware(thunk)
    : applyMiddleware(thunk, createLogger())
let store = compose(middlewares)(createStore)(rootReducer)

/**
 * Helper function to render the Gradebook component to the DOM.
 * Makes the following props available to the Gradebook component:
 *  - storeState: an object of the latest state of the redux store.
 *  - dispatch: a function that dispatches actions to the store/reducer.
 */
const render = (nodeId, component) => {
  let node = document.getElementById(nodeId)
  ReactDOM.render(<component storeState={store.getState()} dispatch={store.dispatch} />, node)
}

/**
 * Function that bootstraps the app.
 *  - render the component with initial store state.
 *  - re-render the component when the store changes.
 */
const start = () => {
  render('app', MyComponent)
  store.subscribe(() => render('app', MyComponent))
}

To start the application, (a) load the bundle on the page and (b) call the exported start function when the page loads. Here’s an example using jQuery:

{% load render_bundle from webpack_loader %}
{% render_bundle 'myapp' %}

<script>$(() => MyCompanyApp.start())</script>

Explanation

This little proof-of-concept is interesting to me because of how much usefulness it provides with so little code.  With this code, we create a Redux store with some basic middlewares and a reduce that does nothing interesting (yet).  Then we render a component to the DOM, giving it the current store state and a function for the component to dispatch actions if necessary, and setting up a store subscription so that the component will be re-rendered whenever the store changes.

Another cool part about this approach is that a lot of the setup code can be pulled out and made reusable.  The render(), start(), and store setup would probably be the same for every Mini App we would create.  Then we could simplify this whole file down to just the reducer and passing in the node and component to the start function (not implemented here).

Conclusion

With very little effort and Boilerplate, we have a React application using Redux as it’s storage system.  With this in place, you can build quite sophisticated widgets and still have the flexibility to get more complex if you need to do something more involved.

Advertisements
Implementing a Mini-React-Redux Framework on a Django Page

Adding Webpack Bundles to your Django-Mako-Plus (DMP) Site

This post describes how to hook up Webpack to a Django site using the django-webpack-loader tool in the special case where your Django site is running the Django-Mako-Plus (DMP) library.

Why Webpack?

In the last few years, the ecosystem of JavaScript build tools has grown in both size and quality.  One of my favorite build tools is Webpack.  If you have not heard of it, I highly recommend it to you for bundling your JavaScript, CSS, and other static assets.  To get the most out of this post, please go do a little cursory research on the use case of the webpack bundler before continuing on here.

I also appreciate the Django framework for building dynamic web applications in Python. If you would like to use Django with Webpack, it takes a little extra work to get things hooked together in a clean, scalable way.  Webpack outputs “bundles” that can be formatted in many ways (Common.js, UMD, Require.js, etc.) depending on how they should be consumed and can even output the bundles with the md5 hash in the name to improve the caching of your bundles on the internet.

What is “django-webpack-loader”?

Django, for all its great features, handles static files poorly by modern standards which is where the django-webpack-loader (hereafter referred to as “the loader”) tool comes in.  It provides a way to load a webpack bundle by name into a Django template by mapping the webpack bundle’s “logical name” (e.g. main) to it’s filename (e.g. main-be0da5014701b07168fd.js) which filename changes whenever the contents of the bundle change.  To learn how the loader works, read the documentation and tutorial.

DMP with The Loader

The loader integrates with the templating system of Django.  If you are using Django-Mako-Plus (DMP), you replaced the default templating engine with Mako so the prepared require_bundle helper is not available anymore.  Lucky for us, Mako is so powerful that we can import python functions with ease.  All we need to do in a template is import the right function and call it using Mako syntax:

<%! from webpack_loader.templatetags.webpack_loader import render_bundle %>
<html>
  <head> 
    ${ render_bundle('main') }
  ...

Simple! We can even simplify this a bit by adding the import statement as a DEFAULT_TEMPLATE_IMPORTS for our Mako templates like so:

TEMPLATES = [
  {
    'BACKEND': 'django_mako_plus.MakoTemplates',
    'OPTIONS': {
      # Import these names into every template by default
      # so you don't have to import them explicitly
      'DEFAULT_TEMPLATE_IMPORTS': [
        'from webpack_loader.templatetags.webpack_loader import render_bundle',
      ]
    }
  }
]

BAM!

Conclusion

All done!  You are now ready to start using the django-webpack-loader to include Webpack bundles in your Django-Mako-Plus website!

 

Adding Webpack Bundles to your Django-Mako-Plus (DMP) Site

Deploying to Production with Git

On your last day at any job, it is fun to go and change a bunch of things and then leave it all with your colleagues and say “Peace Out!” One thing you could do is rewrite/rework the project’s build and deploy process. Here is a way you could do it using Git with a Django project using Nginx and UWSGI (and yes, I did this all on my last day :).

The process is (1) automate our build process with a Makefile, (2) setup a Git repo on the live server to push to, and (3) use a Git hook to automatically call our Makefile targets.

Build with a Makefile

First things first, lets write a Makefile because they are very helpful. You can replace a Makefile with some other script or set of scripts, but I find Makefiles to be a very good idea on Unix based systems. You just need something to automate the your build process.

Here is an excerpt that is pretty similar to the Makefile I used:

# Makefile for building and deploying
#

UWSGI=/etc/init.d/uwsgi
NGINX=/etc/init.d/nginx

deploy: dependencies clean minified_static_files

restart: $(UWSGI) $(NGINX)
	$(UWSGI) restart
	$(NGINX) restart

stop: $(UWSGI) $(NGINX)
	$(NGINX) stop
	$(UWSGI) stop

dependencies: dependencies.pip
	pip install -r dependencies.pip # Or requirements.txt

resources:
	python resources_build.py # Minifies static files.

minified_static_files: resources
	python manage.py collectstatic # Collect into static_files/

clean:
	@-rm -rf static_files/
	@-find . -name '__pycache__' -exec /bin/rm -rf {} ;
	@echo 'Successfully Cleaned!'

.PHONY: clean resources dependencies restart stop deploy

You can put any useful commands that you run often in your project during development or when deploying to production. Put this in the root of your project directory or anywhere else in your Git project so you will not lose it.

Setup Production Git Repo

Lets use a Bare Git Repository. Log in to your production server, create a new directory, and initialize it as a bare git repository.

mkdir prod-repo
cd prod-repo
git init --bare

You will need to add this repository to your Git remotes on your local machine. The command looks something like this:

git remote add production ssh://username@www.yourserver.com:PORT/path/to/prod-repo

FYI: The PORT is whatever port you run your ssh server on.

Post-Receive Git Hook

Now lets setup a post-receive git hook on the production bare git repository that will call your makefile (or other automatic script) once a push has been received.

vim prod-repo/hooks/post-receive

A git hook file is any executable script, so you can write it in bash, sh, python, ruby, etc. Lets keep it simple and use sh.

#!/bin/sh
#
# SOURCE_DEST is whatever directory you have configured
# uwsgi look for your app in.  This is where Git will put
# the new source files that you push to this repo.

# Variables
SOURCE_DEST=/path/to/source
GIT_DIR=/path/to/prod-repo

# Update the HEAD to latest commit
git --work-tree=$SOURCE_DEST --git-dir=$GIT_DIR checkout -f

cd $SOURCE_DEST

# Run make targets
make deploy
make restart

# Fix permissions for Code
chown -R www-user $SOURCE_DEST
chgrp -R www-user $SOURCE_DEST

Putting it all Together

To update production, just issue a git push command to your production remote:

git push production master

This will push your changes on git and run your post-receive git hook script which calls your Makefile targets. Customize this to fit your needs. You can easily add in targets to run database migrations, compile coffeescript, pre-process CSS for SASS or Less, run unit tests, etc. The sky is the limit. It would also be a good idea to use a git tag each time before you push to production. Consider using some client-side git hook to accomplish that 🙂

Deploying to Production with Git