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'),
}
}
- Now to set up a new view such as http://localhost/your-react-view/ in urls.py that simply returns the template file "react.html".
- 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. This guide will probably be out of date by the time you finish reading it!
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/media/client/(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.
- Terminate 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$/,
loader: ExtractTextPlugin.extract('style', 'css?-autoprefixer!postcss!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 unless you have something in place for specifically your map files.
Changes to client/config/paths.js
- In getPublicUrl(), change the one liner to:
return envPublicUrl || require(appPackageJson).homepage || '/media/';
- Scroll to the bottom to find "module.exports"
- Comment out or remove appHtml, we don't need it anymore.
- Change appBuild to:
appBuild: resolveApp('../media/'),
- And add these to module.exports:
appBuildDev: resolveApp('build'),serverHostname: 'localhost',
Changes to client/scripts/build.js
Replace the following line
fs.emptyDirSync(paths.appBuild);
With:
fs.emptyDirSync(paths.appBuild + '/client/');
This line empties the output folder each time you run yarn build. The change ensures your existing media folder doesn't get wiped each time.
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 defining styles.
Testing your setup
Dev setup
- Build your project:
yarn start
- And run Django as per usual:
python manage.py runserver
- In your browser, hit up http://localhost/your-react-view/ and you should see your Django template using the webpack bundles and the React demo code!
Production setup
- Build your project:
- In your production 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/media/client/
- On your browser, hit up http://localhost/your-react-view/ and you should see your site serving files from the /media/client/ folder.
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.
Disabling chunk hash on output filenames
For production, sometimes you may find it useful to not include hashes in your code filenames.
In webpack.config.prod.js:
- output.filename: 'client/js/[name].js'
- plugins at the end: new ExtractTextPlugin('client/css/[name].css')
I would definitely keep it in for media files though as they don't change often.
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
- Using Webpack transparently with Django + hot reloading React components as a bonus
- Thanks to gaearon (Dan Abramov) for fixing the hot reload bug
Updates
22/05/2017
- Fixed output folder from static to media
- Fixed bug with SCSS output in the wrong order
- Removing file hashes from output.
- Removed the section about /static/static
10/03/2017
- Forgot to remove ManifestPlugin from webpack.config.prod.js
- Added module.exports > resolve > root