Django + React难题的缺失部分
项目描述
django-cra-helper
简介
django-cra-helper 是连接 Django 和 create-react-app 的缺失环节。通过将其添加到您的Django项目中,您可以几乎无缝地将React组件注入Django模板,并通过Django上下文变量初始化组件属性。
该软件包的最终目标是尽可能减少对通常在开发过程中使用的Django或create-react-app的工作流程的更改,以集成这两个项目。从 npm start
到 python manage.py collectstatic
,您的命令应按预期工作,这样您就可以忘记实现细节并回到开发中!
注意:为了本README的目的,缩写 CRA 将用于指代 create-react-app。
安装
此软件包可通过 pip
安装
pip install django-cra-helper
配置
1. settings.py
一旦安装了 django-cra-helper,则需要将 cra_helper
添加到 settings.py
中的 INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'cra_helper',
'django.contrib.staticfiles',
]
注意:
cra_helper
必须 在列表中位于django.contrib.staticfiles
之上!
将 cra_helper.context_processors.static
添加到 TEMPLATES['OPTIONS']['context_processors']
TEMPLATES = [
{
# ...snip...
'OPTIONS': {
'context_processors': [
# ...snip...
'cra_helper.context_processors.static',
],
},
},
]
此外,必须将以下 STATICFILES_FINDERS
列表添加到 settings.py
STATICFILES_FINDERS = [
# Required for CRAManifestFinder below to work
'django.contrib.staticfiles.finders.FileSystemFinder',
# A finder to pull in asset-manifest.json
'cra_helper.finders.CRAManifestFinder',
]
最后必要的设置是包含CRA项目文件的文件夹名称,相对于Django 项目(包含manage.py
的文件夹)的基准目录。
CRA_APP_NAME = 'cra-app'
如果由于某种原因,CRA liveserver不在localhost或端口3000上提供服务,可以将以下设置添加到settings.py
中,以指定其实际的主机和端口
CRA_HOST = '0.0.0.0' # defaults to 'localhost'
CRA_PORT = 9999 # defaults to 3000
2. urls.py
可以通过首先将以下内容添加到您的项目或应用的urls.py
文件中启用热重载支持
# import Django settings
from django.conf import settings
# add `re_path` import here
from django.urls import path, re_path
# ...other imports...
from cra_helper.views import proxy_cra_requests
# other existing urls
urlpatterns = [...]
# add a reverse-proxy view to help React in the Django view talk to Create-React-App
if settings.DEBUG:
proxy_urls = [
re_path(r'^__webpack_dev_server__/(?P<path>.*)$', proxy_cra_requests),
re_path(r'^(?P<path>.+\.hot-update\.(js|json|js\.map))$', proxy_cra_requests),
]
urlpatterns.extend(proxy_urls)
接下来,根据您的项目版本的react-scripts
,遵循以下说明
对于使用react-scripts@<3.3.0
的项目
将一个额外的url添加到上面的proxy_urls
proxy_urls = [
# ...snip...
re_path(r'^sockjs-node/(?P<path>.*)$', proxy_cra_requests),
]
对于使用react-scripts@>=3.3.0
的项目
在Create-React-App文件夹的根目录下创建一个包含以下环境变量的.env
文件
WDS_SOCKET_PORT=3000
3. asset-manifest.json
最后,运行CRA的npm run build
命令一次以生成一个build/
目录。django-cra-helper需要其中的build/asset-manifest.json
文件来帮助加载可能用于任何React组件的非JS和非CSS资产。每次向项目中添加新的非JS或非CSS资产时,应重新运行此命令。
开发
如果CRA项目的liveserver通过npm start
启动,然后再通过python manage.py runserver
启动Django的开发服务器,React代码库中的代码更改将立即更新到Django视图中。
当CRA liveserver运行时,django-cra-helper添加一个bundle_js
数组模板变量,该变量可以插入到Django视图的模板中,以加载包含所有当前JS和CSS的liveserver的各个文件。这些文件在编辑React代码时由liveserver即时重新编译。此文件可以按以下方式添加到Django模板中
{% if bundle_js %}
{% for file_url in bundle_js %}
<script type="text/javascript" src="{{ file_url }}"></script>
{% endfor %}
{% endif %}
注意:这里不要使用
static
模板标签!此文件需要从CRA liveserver加载。
生产
django-cra-helper还负责确保Django的collectstatic
命令包含由CRA的npm run build
命令构建的生产就绪的包。
首先,使用典型的CRA npm
构建命令为React文件准备生产环境
npm run build
这将在CRA项目文件夹中的/build/
文件夹输出捆绑、最小化的JavaScript和CSS以及资产。
此命令完成后,运行以下Django命令以收集静态文件,包括编译后的React资产
python manage.py collectstatic --no-input
React资产将与其他静态资产一起包含在settings.STATIC_ROOT
目录中,在Django生产环境中按常规提供服务。还会拉入一个asset-manifest.json
文件。此CRA生成的文件的内容由django-cra-helper需要,以帮助引用在构建过程中为其文件名添加了唯一哈希的React文件。
类似于前面提到的bundle_js
模板变量,当CRA liveserver没有运行时,django-cra-helper还包括许多其他模板变量
对于使用react-scripts@>=3.2.0
的项目
从react-scripts@3.2.0
开始,可以在asset-manifest.json
中找到新的entrypoints
属性。它包含一个文件数组,这些文件由django-cra-helper
在模板中提供,以便更容易通过新的entrypoints.css
和entrypoints.js
数组注入这些文件
{% for file in entrypoints.css %}
<link href="{% static file %}" rel="stylesheet">
{% endfor %}
{% for file in entrypoints.js %}
<script type="text/javascript" src="{% static file %}"></script>
{% endfor %}
注意:这些JavaScript和CSS文件应该按网站加载所需的顺序排列;最终顺序是从
asset-manifest.json
中存在的顺序导出的。
对于使用react-scripts@>=3.0.0 to react-scripts@<3.2.0
的项目
在较晚版本的react-scripts
中引入了代码拆分,将main_js
拆分为多个文件。需要添加额外的<script>
标签以启用React项目加载。根据项目的大小,除了上面提到的main_js
之外,您还需要按特定顺序添加至少另外两个标签
{% if main_js %}
<script type="text/javascript" src="{% static runtime_main_js %}"></script>
<script type="text/javascript" src="{% static static_js_2_9a95e042_chunk_js %}"></script>
<script type="text/javascript" src="{% static main_js %}"></script>
{% endif %}
上述 static_js_2_9a95e042_chunk_js
的命名会因项目而异。遗憾的是,您需要在项目的 asset-manifest.json 中手动确认此值并进行相应的更新。不过,这似乎不会在不同构建之间改变,因此可能不是您需要定期更新的值...
对于使用 react-scripts@<=2.1.8 的旧项目
最重要的两个变量是 main_js
和 main_css
。这些可以通过模板中的典型调用 {% static %}
注入到页面中
{% if main_css %}
<link href="{% static main_css %}" rel="stylesheet">
{% endif %}
{% if main_js %}
<script type="text/javascript" src="{% static main_js %}"></script>
{% endif %}
注意:尝试使用
react-scripts@2.1.8
构建一个全新的 CRA 项目,但未能重建只允许一个main_js
的 SPA。使用npm run build
生成的工件与下面详细说明的react-scripts@3.1.2
生成的工件几乎相同。可能存在
react-scripts
的子依赖项,使得无法启动按照上述说明工作的应用程序。在这种情况下,请尝试下一节中的说明。
支持CRA的相对路径
CRA 允许开发者通过 package.json 中的 "homepage"
属性指定一个相对子文件夹,以便从该文件夹托管其站点
{
"name": "cra-app",
"version": "0.1.0",
"homepage": "/frontend",
...
}
设置此值后,npm run build
将输出带有路径前缀的资源和 asset-manifest.json
Before: /static/js/main.319f1c51.chunk.js
After: /frontend/static/js/main.319f1c51.chunk.js
为了确保 React 导入/资产等...可以在通过 Django 托管时找到,还需要更新 Django 的 settings.py 中的 STATIC_URL
以包含路径前缀
STATIC_URL = '/frontend/static/'
CRA_PACKAGE_JSON_HOMEPAGE = '/frontend'
上面设置的 CRA_PACKAGE_JSON_HOMEPAGE
值应与 package.json 中的 "homepage"
值匹配,以便 django-cra-helper 可以找到 CRA liveserver 并相应地重定向
一旦做出这些更改,React 应用程序应该能够找到它需要的所有内容。
React在Django模板中
通过模板上下文指定React组件
CRA 项目需要进行一点小的重构,以便在 Django 服务器视图时能够接受通过上下文传递的输入值。以下是一个示例,说明如何通过修改 CRA 项目的 src/index.js 文件,在 src/index.js 文件中进行一些小的调整,从而为 Django 与打包的 React 代码库之间的通信建立一个简单的 API
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
/**
* Maintain a simple map of React components to make it easier
* for Django to reference individual components
*/
const pages = {
App,
}
/**
* If Django hasn't injected these properties into the HTML
* template that's loading this script then we're viewing it
* via the create-react-app liveserver
*/
window.component = window.component || 'App';
window.props = window.props || { env: 'Create-React-App' };
window.reactRoot = window.reactRoot || document.getElementById('root');
/**
* React the component as usual
*/
ReactDOM.render(
React.createElement(pages[window.component], window.props),
window.reactRoot,
);
基本上,index.js
被更新为读取 window.component
、window.props
和 window.reactRoot
中设置的值,并使用这些值来渲染组件。这三个“输入”中的每一个都将允许 Django 容易地指定每个视图初始化哪个组件
window.component
:指向pages
中的组件条目的 字符串window.props
:包含要传递给组件的属性的 对象window.reactRoot
:一个document.getElementById
的 实例
注意:设置这些值是可选的。模板中指定的默认值使得组件在从 CRA liveserver 观看时能够按预期渲染。
“API”就绪后,Django 视图可以通过传递给模板的上下文包括这些输入值
def index(request):
context = {
'component': 'App',
'props': {
'env': 'Django',
},
}
return render(request, 'index.html', context)
以下是 Django 应用程序视图的 index.html 模板,它可以渲染多个版本的 react-scripts
(仅用于与新的 CRA 应用程序进行演示目的)
{% load static %}
<!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">
{% if entrypoints %}
{% for file in entrypoints.css %}
<link href="{% static file %}" rel="stylesheet">
{% endfor %}
{% elif main_css %}
<link href="{% static main_css %}" rel="stylesheet">
{% endif %}
<title>Django + React Project</title>
</head>
<body>
<div id="react">Loading...</div>
{{ props | json_script:"react-props" }}
<script>
window.component = '{{ component }}';
window.props = JSON.parse(
document.getElementById('react-props').textContent
);
window.reactRoot = document.getElementById('react');
</script>
{% if bundle_js %}
{% for file in bundle_js %}
<script type="text/javascript" src="{{ file }}"></script>
{% endfor %}
{% elif entrypoints %}
{% for file in entrypoints.js %}
<script type="text/javascript" src="{% static file %}"></script>
{% endfor %}
{% elif main_js %}
<script type="text/javascript" src="{% static runtime_main_js %}"></script>
{# make sure to update this accordingly according to asset-manifest.json #}
<script type="text/javascript" src="{% static static_js_2_597565cd_chunk_js %}"></script>
<script type="text/javascript" src="{% static main_js %}"></script>
{% endif %}
</body>
</html>
上下文的 component
和 props
分别绑定到 window.component
和 window.props
注意设置 windows.props
时使用 json_script
过滤器。Django 提供了这个过滤器,作为一种将 Python dict
转换为 Javascript Object
的简单方法。注入的 <script>
标签的内容可以通过 JSON.parse()
运行,以安全地将其分配给变量。
window.reactRoot
指定了 React 组件应渲染到的容器元素。只有在容器的 id
不是 "root"(与 CRA 项目的 index.html
中分配给容器的 <div>
的 ID 相同)时,才需要设置此值。
合并Django和React路由
在某些场景中,可能会希望利用React客户端路由和典型的Django路由。幸运的是,定义一个额外的“通配符”Django路由来优雅地处理应由React应用程序的路由处理的URL非常简单。
在一个典型的客户端路由设置中,React应用程序可能定义了以下路由:
import { BrowserRouter as Router, Switch, Route, NavLink, Redirect } from 'react-router-dom';
const App = () => (
<Router>
<ul>
<li><NavLink to='/'>To Home</NavLink></li>
<li><NavLink to='/foo'>To Foo</NavLink></li>
<li><NavLink to='/bar'>To Bar</NavLink></li>
</ul>
<Switch>
<Route path='/foo'>foo</Route>
<Route path='/bar'>bar</Route>
<Route exact path='/'>home</Route>
<Route path='*'>
<Redirect to='/' />
</Route>
</Switch>
</Router>
);
在Django方面,可以在“frontend”应用程序的frontend/urls.py
中定义以下路由,这些路由都指向一个渲染frontend/templates/index.html
的index
视图
from django.urls import path, re_path, include
from .views import index
urlpatterns = [
# Default index template to render index.html
path('', index),
# Helps Django pass unknown routes to the client-side router
re_path(r'^.*/$', index)
]
然后,可以将这些URL包含到项目的根urls.py
中
from django.urls import path, include
urlpatterns = [
# ...other routes...
path('', include('frontend.urls')),
]
注意:如果您的路由不起作用,请确保已将
'frontend'
添加到settings.py
中的INSTALLED_APPS
有了这两个路径,当Django尝试处理只定义为客户端路由的路径时,现在将加载React应用程序。
引用React静态文件
CRA捆绑的其他资产,包括图像资产,可以通过将文件路径中的/
、.
、~
和-
替换为_
来在模板中访问。 django-cra-helper将asset-manifest.json
中的每个条目添加到基本上下文,使用这些替换规则来适应Django的static
标签。
例如,CRA项目中的一个logo.svg
文件可以按如下方式包含在Django模板中:
<!-- This file is located at `/build/static/media/logo.svg` -->
<img src="{% static static_media_logo_svg %}" height="40" width="40">
注意:这是可选的!静态资产仍然可以包含在Django应用程序的
/static/
目录中,并按常规加载。上述特殊替换仅在重用React资产而不仅限于特定组件时需要。
收益揭晓
到此为止,React组件现在应该在CRA liveserver和通过Django提供时都渲染并可查看。以下是一个略微修改的CRA App
组件在CRA(左)和Django(右)中显示的示例
项目详情
下载文件
下载适用于您的平台文件。如果您不确定要选择哪一个,请了解更多关于安装软件包的信息。