Overview
In this tutorial we will create a multipage web application using django, react and webpack.
This means:
- Instead of handling routes by react, django will handle all the routes and once a route has been loaded, its upto you whether you want react to create different routes for that particular page or not.
- Webpack will handle all the staticfiles headache i.e., all the optimizing stuff such as compressing and minifying.
- Static files bundled by webpack will be served by django.
Let's start
Repository: https://github.com/nitinraturi/django-react-webpack
Dependencies
- Python3 & Pip (https://raturi.in/blog/installing-python3-and-pip3-ubuntu-mac-and-windows/)
- Node, npm, npx (https://raturi.in/blog/nodejs-and-npm-proper-installation-and-uninstallation-ubuntu/) - This link contains uninstallation and installation, follow the installation one.
How to create it from scratch
Install python requirements:
- Django (you can install any version, I prefer 2.2 because of its long term support)
pip install django==2.2
- Django Webpack Loader - It consumes the output generated by webpack-bundle-tracker and lets you use the generated bundles in django.
pip install django-webpack-loader
Create a django project
django-admin startproject conf .
Create required directories
mkdir {templates,static,frontend}
templates - To serve django templates
static - To serve django static files
frontend - We will install react project inside this directory
Your directory structure will be something like this:
.
├── conf
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── frontend
├── manage.py
├── static
└── templates
Now configure settings.py and urls.py
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static_root")
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
# urls.py
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
]
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL,
document_root=settings.STATIC_ROOT)
Create a new app pages
python manage.py startapp pages
Add it in INSTALLED_APPS inside settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'pages'
]
Now inside pages/views.py create two views to serve different urls
# pages/views.py
from django.shortcuts import render
def index1(request):
return render(request, 'index1.html', {})
def index2(request):
return render(request, 'index2.html', {})
Create a files urls.py inside pages app and two urls inside pages/urls.py to serve above two views.
touch pages/urls.py
# pages/urls.py
from django.urls import path
from .views import index1, index2
app_name = 'pages'
urlpatterns = [
path('', index1, name='index1'),
path('index2', index2, name='index2')
]
Now update conf/urls.py and link pages/urls.py
# conf/urls.py
from django.contrib import admin
from django.urls import path,include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('pages.urls',namespace='pages')),
]
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL,
document_root=settings.STATIC_ROOT)
Now create the above two html files index1.html and index2.html inside templates directory
<!-- templates/index1.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Django React Webpack - Raturi.in</title>
</head>
<body>
<ul>
<li><a href="{% url 'pages:index1' %}">Index1</a></li>
<li><a href="{% url 'pages:index2' %}">Index2</a></li>
</ul>
<center>
At index1
</center>
</body>
</html>
<!-- templates/index2.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Django React Webpack - Raturi.in</title>
</head>
<body>
<ul>
<li><a href="{% url 'pages:index1' %}">Index1</a></li>
<li><a href="{% url 'pages:index2' %}">Index2</a></li>
</ul>
<center>
At index2
</center>
</body>
</html>
Now django's part is done, we have to configure react and webpack. you can verify the changes by running the server
python manage.py runserver 0:8080
Let's setup react part
Change your directory to frontend
cd frontend/
Now create a react app using
npx create-react-app .
Now install required npm packages
npm install --save-dev webpack webpack-cli webpack-bundle-tracker babel-cli babel-core babel-loader babel-preset-env babel-preset-es2015 babel-preset-react babel-register
Now create two files webpack.config.js and .babelrc and a directory for storing your webpack generated bundles inside fronted as assets/dist
touch .babelrc webpack.config.js
mkdir assets assets/dist
Now configure webpack.config.js .babelrc package.json
webpack.config.json
const path = require("path");
const BundleTracker = require('webpack-bundle-tracker');
var config = {
context: __dirname,
entry: {
'staticfiles': './src/index.js',
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src/'),
'@django': path.resolve(__dirname, '../static/'),
}
},
output: {
path: path.join(__dirname, './assets/dist'),
filename: "[name]-[hash].js",
publicPath: '/static/dist/'
},
plugins: [
new BundleTracker({ filename: './webpack-stats.json' }),
],
devtool: 'cheap-module-eval-source-map',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
include: [
path.resolve(__dirname, "src"),
],
},
{
test: /\.css$/,
use: [
'style-loader', 'css-loader'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader',
],
},
]
}
}
module.exports = (env, argv) => {
if (argv.mode === 'production') {
config.devtool = 'none';
}
return config
};
.babelrc
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
package.json
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1"
},
"scripts": {
"build": "./node_modules/.bin/webpack --mode=production --config webpack.config.js",
"watch": "npm run start -- --watch",
"start": "./node_modules/.bin/webpack --mode=development --config webpack.config.js"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-loader": "^8.1.0",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-register": "^6.26.0",
"webpack": "^4.42.1",
"webpack-bundle-tracker": "^0.4.3",
"webpack-cli": "^3.3.11"
}
}
Now in your conf/settings.py add webpack related settings
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'pages',
'webpack_loader', # add this
]
FRONTEND_DIR = os.path.join(BASE_DIR,'frontend') # path to frontend directory
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static_root")
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
os.path.join(FRONTEND_DIR, "assets"), # path to webpack generated files
)
# Status of files generated by webpack
WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'dist/', # must end with slash
'STATS_FILE': os.path.join(FRONTEND_DIR, 'webpack-stats.json')
}
}
Now update your templates index1.html and index2.html to load webpack generated files something like this
{% load render_bundle from webpack_loader %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Django React Webpack - Raturi.in</title>
{% render_bundle 'staticfiles' 'css'%}
</head>
<body>
<ul>
<li><a href="{% url 'pages:index1' %}">Index1</a></li>
<li><a href="{% url 'pages:index2' %}">Index2</a></li>
</ul>
<center>
At index1
</center>
{% render_bundle 'staticfiles' 'js' %}
</body>
</html>
In the above code, render_bundle 'staticfiles': staticfiles is the name of the entrypoint and render_bundle will load the files generated by webpack inside dist folder.
How to run the server
Run django normally with runserver command
To run react open a seperate terminal inside frontend directory and run
npm run watch # this command will automatically look for changes for static files and will update the dist folder. # You have to just refresh your browser where django is running.
- In case of production run
npm run build
- Finally check the source code of html file in browser you can see webpack generated files inside dist folder.
That all.