Easily Integrate React.js to a Django Website for dev and production builds

Assuming you already have a Django website up and running, this will get you started with compiled React.js on the front-end with:

  • Django and Webpack integrated nicely together during development and deployment
  • Support for React.js code in your website
  • Packaged and obfuscated Javascript code
  • SCSS support
  • Proper sourcemaps for dev builds
  • Hot reloading of (S)CSS changes during development, automatic page reloading for JS changes

Throughout this tutorial, it treats the client (browser) and django (server) projects as separate codebases. I'll try to be as clear as possible to which one we're currently working on.

This post was meant to be written much earlier, but things happened...

Zelda: Breath of the Wild took over my free time

      Setup

      • Grab the latest Node.js (6.10.0)
      • Make sure npm is up to date. Comes with Node if you upgraded it (currently using 3.10.8)
      • Install yarn globally (currently using 0.21.3)

      npm install -g yarn

      • Install create-react-app globally (currently 0.9.4 on github, but install says 1.3.0? so confusing)

      npm install -g create-react-app

      Note: don't use 0.9.3 if you want hot-reloading without some extra work.

      Prepping Django for integration with Webpack

      This side is much simpler, so we'll get it out of the way first.

      • In your Django project folder, install "django-webpack-loader" (v0.3.3 at time of writing):

      pip install django-webpack-loader

      • Add "webpack_loader" to INSTALLED_APPS in settings.py
      • Now to add WEBPACK_LOADER in settings.py. Take note that webpack loader will load different files depending on the value of settings.DEBUG.

      # Webpack loader
      WEBPACK_LOADER = {
          'DEFAULT': {
              'BUNDLE_DIR_NAME': 'bundles/',
              'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats-dev.json' if DEBUG else 'webpack-stats-live.json'),
          }
      }

      • Using your knowledge of setting up Django views and templates, point "your-react-view" url to this basic template react.html

      from django.shortcuts import render

      def your_react_view(request):

          return render(request, 'react.html')

      • The react.html file should contain:

      <!doctype html>
      < html lang="en">
        <head>
          <meta charset="utf-8" />
          <title>Testing</title>
        </head>

        <body>
          <div id="root"></div>

          {% load render_bundle from webpack_loader %}      
          {% render_bundle 'main' %}
        </body>
      < /html>

      In the template is simply an empty html5 document which imports the "webpacked" files and an empty div with the ID "#root" is used for the mounting point for the React code (see client/src/index.js).

      It also imports the "main" bundle compiled by webpack and executes it.

      Those are the 3 most important lines as they bring in the webpacked content to the page of your liking.

      Creating a front-end React/Webpack project

      Now for the nitty gritty. All the jokes and complaints you've heard about JS compilation and complexity are somewhat true.

      But don't worry, this tutorial will get you through the hardest part (the setup) by being as clear as possible, letting you know what you're changing and why.

      In a console, go to your Django project folder and type:

      create-react-app client
      cd client/
      yarn start

      I'm using the name "client" for the browser project, but you can quite easily call it "frontend" or whatever else you fancy.

      After typing "yarn start", it should pop up a browser tab/window while compiling and show you that it's working.

      At the moment it's a completely separate website to your Django website, so we'll have to make some changes to link them together.

      Ejecting from  create-react-app bootstrap

      So far it's very barebones and there are no files in client project that you can modify to change the build process.

      To make any sort of customisations, you will need to eject the project so there are files to configure. This means leaving the safety nest of create-react-app. It's a one way process, but you'll be far better off afterwards.

      • In the console for the client project, type in:

      yarn eject

      • Press Y to confirm (I got an error at the end of it, but the ejection process seemed to work just fine)
      • Now looking through the client project folder, you have lots of new files to modify!
      • Let's test that the client project still works with:

      yarn start

      Customisations

      By the time we're done with this section, your config should:
      • create production build to your-django-project/static/(css|js|media)
      • use SCSS in your code (it's great for managing nested CSS)
      • produce a webpack-stats-(dev|prod).json file for django integration
      • have a working dev build integrated with Django (served via websockets, so no output folder)

      Before we get started, there are some downloads needed for the extra features we want.

      • In the client project folder, type in:

      yarn add webpack-bundle-tracker node-sass sass-loader

      Current versions are:

        • webpack-bundle-tracker@0.2.0
        • sass-loader@6.0.2
        • node-sass@4.5.0

      While we're waiting, lets get rid of the annoying a browser tab that opens everytime we run yarn start!

      • Open up client/scripts/start.js and search for any lines of code with openBrowser
      • There should only be two lines using it; the import and call.
      • Exterminate with extreme prejudice.

        Changes to client/config/webpack.config.dev.js

        • Under "var paths = require('./paths')", add in:

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

        • Replace "var publicPath = '/';" with:

        var publicPath = 'http://' + paths.serverHostname + ':3000/assets/bundles/'; // override django's STATIC_URL for webpack bundles

        • Change module.exports > output > path to paths.appBuildDev
        • Under module.exports > resolve, add in root: paths.appSrc,
        • Under module.exports > module > preloaders, under 'include' add in:

        exclude: /node_modules/

        There is no need to lint check modules that we shouldn't be changing.

        • Under module.exports > module > loaders > first entry > exclude, change:

        /\.css$/,

        to

        /\.(css|scss)$/,

        • Under module.exports > module > loaders, add in the following after the .css test:

        // SCSS support
        {
          test: /\.scss$/,
          loaders: ["style", "css", "sass"]
        },

        • Search for HtmlWebpackPlugin and disable/remove it
        • And at the very end of module.exports > module > plugins, add in:

        // For django to know about webpack
        new BundleTracker({ filename: '../webpack-stats-dev.json' })

        Note: the dev specific filename

        • Search for devtool and change it from 'cheap-module-source-map' to 'source-map'.

        We don't want to go cheap while we're debugging! More information is always better.

        Changes to client/config/webpack.config.prod.js

        • Under "var paths = require('./paths')", add in:

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

        • Under module.exports > module > preloaders, add the follow under 'include':

        exclude: /node_modules/

        There is no need to lint check modules we shouldn't be changing.

        • Under module.exports > resolve, add in root: paths.appSrc,
        • Under module.exports > module > loaders > first entry > exclude, change:

        /\.css$/,

        to

        /\.(css|scss)$/,

        • Under module.exports > module > loaders, and after the .css test, add in:

        // SCSS support
        {
          test: /\.scss$/,
          loaders: ["style", "css", "sass"]
        },

        • Search for HtmlWebpackPlugin and disable/remove it
        • Same with ManifestPlugin
        • And at the very end of module.exports > module > plugins, add in:

        // For django to know about webpack
        new BundleTracker({ filename: '../webpack-stats-prod.json' })

        Note: the production specific filename

        • I'd strongly suggest disabling sourcemaps by commenting out "devtool" for production so people can't view your plain text source code.

        Changes to client/config/paths.js

        • Scroll to the bottom to find "module.exports"
        • Comment out appHtml, we don't need it anymore.
        • Change appBuild to:

        appBuild: resolveApp('../static'),

        • And add these to module.exports:
        appBuildDev: resolveApp('build'),   

        serverHostname: 'localhost',

        Changes to client/scripts/build.js and client/scripts/start.js

        • Remove paths.appHtml from

        if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {

        You can also take the time to remove the files:

        • client\public\favicon.ico
        • client\public\index.html

        Phew, we're mostly done with the configuration!

        Switching to SCSS

        Now let's switch to SCSS. Rename client/src/App.css to client/src/App.scss and update the import in App.js from:

        import './App.css';

        to:

        import './App.scss';

        It's a relatively minor change, but makes a world of difference when writing neatly nested CSS classes. You can also define variables and macros/functions when definining styles.

        Testing your setup

        Dev setup

        • Build your project:

        yarn start

        • And run Django as per usual:

        python manage.py runserver

        Production setup

        • Build your project:
        • In your settings.py file, make sure you're reading webpack information from webpack-stats-prod.json in the WEBPACK_LOADER setting.
        • Compile your production browser project by typing:

        yarn build

        • Check for output in DjangoPath/static/static/ (yes, that is not a typo. More details towards the end)

        Enabling hot loading

        You may have noticed some 404's in the Django log to /sockjs-node/info while testing your setup.

        That's webpack hot-loading looking for the hot-reload websocket signal in the wrong place.

        In webpack.config.dev.js, disable/remove the line:

        require.resolve('react-dev-utils/webpackHotDevClient')

        And replace it with:

        require.resolve('webpack-dev-server/client') + '?http://' + paths.serverHostname + ':3000',
        require.resolve('webpack/hot/dev-server'),

        The default webpackHotDevClient provided by create-react-app is neat, but isn't configurable so makes it's useless for our setup. So we have to switch it out for the one that comes with webpack-dev-server which allows us to set the webpack server location.

        Hot-reload bug with create-react-app 0.9.3

        Please note that there is a bug which prevents webpack-dev-server/client from working properly with create-react-app v0.9.3 (or v1.2.1?). The bug was fixed in 0.9.4.

        If you're on v0.9.3 and want hot reloading, you can just fix this yourself by editing webpack.config.dev.js.

        • Find the following line under module > loaders > first entry > exclude:

        /\.(js|jsx)$/,

        • And replace it with:

        /\.(js|jsx)(\?.*)?$/,

        Reason being Webpack was stripping out the query information after the "?" on the line where webpack-dev-server/client was imported. For further information about the bug, see this pull request.

        Other recommendations

        Use JSX file extension

          Switching your React component code to use the file extension jsx instead of js. Some editors will behave better for React specific code.

          • Rename index.js to index.jsx and App.js to App.jsx
          • Remember to change paths.appIndexJs accordingly.

          Reduce or remove one static folder in DjangoPath/static/static

          Having a /static/static output folder is rather redudant if all your front-end content is compilied by the client project, but there is a reason for it.

          Anything in the /static/static output folder is deleted before each build so you shouldn't put any Django content in there!

          If you are serving 100% of your front end code via the client project, then you can remove the second static folder.

          If not, rename the second static folder to something like /static/client rather than removing it.

          The first "static" in /static/static is defined in paths.js as "appBuild". Just leave this one as it is because that's where static content SHOULD go.

          The second static folder can be modified if you'd like. Make the following changes to both client/config/webpack.config.dev.js and webpack.config.prod.js

          Search for "static/" and rename/remove it from the following strings:

          • modules.export > output > filename
          • modules.export > output > chunkFilename
          • modules.export > module > loaders > first loader > query > name
          • modules.export > module > loaders > svg file loader > query > name
          • const cssFilename

              Change dev build output filename from "bundle.js"

              You'll need to change this if you want to support code splitting/chunks.

                Delete logo.svg

                  It's likely you'll be removing the demo code so why not the demo assets too?

                  That's about it for now. For me it's back to Zelda...

                  Sources

                  Updates

                  • Forgot to remove ManifestPlugin from webpack.config.prod.js
                  • Added module.exports > resolve > root
                   
                  Copyright © Twig's Tech Tips
                  Theme by BloggerThemes & TopWPThemes Sponsored by iBlogtoBlog