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==0.7.0
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.