{ "info": { "author": "Matthew Miller", "author_email": "matthew@millerti.me", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3" ], "description": "\n# django-cra-helper\n\n[![PyPI version](https://img.shields.io/pypi/v/django-cra-helper?color=success&logo=pypi&logoColor=white)](https://pypi.org/project/django-cra-helper/)\n\n- [Introduction](#introduction)\n- [Installation](#installation)\n- [Configuration](#configuration)\n - [1. settings.py](#1-settingspy)\n - [2. urls.py](#2-urlspy)\n - [3. asset-manifest.json](#3-asset-manifestjson)\n- [Development](#development)\n- [Production](#production)\n - [Supporting CRA's relative paths](#supporting-cras-relative-paths)\n- [React in Django templates](#react-in-django-templates)\n - [Specifying React Components via template context](#specifying-react-components-via-template-context)\n - [Combining Django and React routes](#combining-django-and-react-routes)\n - [Referencing React static files](#referencing-react-static-files)\n- [The Payoff Revealed](#the-payoff-revealed)\n\n## Introduction\n\n**django-cra-helper** is the missing link between **Django** and **create-react-app**. By adding this to your Django project, you can almost effortlessly inject your React components into your Django templates and initialize component props via Django context variables.\n\nThe ultimate goal of this package is to integrate these two projects with minimal changes to workflows that are typically used with either during development. From `npm start` to `python manage.py collectstatic`, your commands should work as expected so you can forget about implementation and get back to development!\n\n> Note: For the purposes of this README, the abbreviation **CRA** will be used to refer to **create-react-app**.\n\n## Installation\n\nThis package is available for installation via `pip`:\n\n```sh\npip install django-cra-helper\n```\n\n## Configuration\n\n### 1. settings.py\n\nOnce **django-cra-helper** is installed, `cra_helper` will need to be added to `INSTALLED_APPS` in `settings.py`:\n\n```python\nINSTALLED_APPS = [\n 'django.contrib.admin',\n 'django.contrib.auth',\n 'django.contrib.contenttypes',\n 'django.contrib.sessions',\n 'django.contrib.messages',\n 'cra_helper',\n 'django.contrib.staticfiles',\n]\n```\n\n> Note: `cra_helper` **must** be placed above `django.contrib.staticfiles` in the list!\n\nAdd `cra_helper.context_processors.static` to `TEMPLATES['OPTIONS']['context_processors']`:\n\n```python\nTEMPLATES = [\n {\n # ...snip...\n 'OPTIONS': {\n 'context_processors': [\n # ...snip...\n 'cra_helper.context_processors.static',\n ],\n },\n },\n]\n```\n\nAdditionally, the following `STATICFILES_FINDERS` list must be added to `settings.py`:\n\n```python\nSTATICFILES_FINDERS = [\n # Required for CRAManifestFinder below to work\n 'django.contrib.staticfiles.finders.FileSystemFinder',\n # A finder to pull in asset-manifest.json\n 'cra_helper.finders.CRAManifestFinder',\n]\n```\n\nThe last necessary setting is the name of the folder containing the CRA project files, relative to the base directory of the Django **project** (the folder containing `manage.py`):\n\n```python\nCRA_APP_NAME = 'cra-app'\n```\n\nIf for some reason the CRA liveserver does *not* serve on localhost or port 3000, the following settings can be added to `settings.py` to specify its actual host and port:\n\n```python\nCRA_HOST = '0.0.0.0' # defaults to 'localhost'\nCRA_PORT = 9999 # defaults to 3000\n```\n\n### 2. urls.py\n\nHot-reloading support can be enabled by first adding the following to your project or app's **urls.py** file:\n\n```python\n# import Django settings\nfrom django.conf import settings\n# add `re_path` import here\nfrom django.urls import path, re_path\n# ...other imports...\n\nfrom cra_helper.views import proxy_cra_requests\n\n# other existing urls\nurlpatterns = [...]\n\n# add a reverse-proxy view to help React in the Django view talk to Create-React-App\nif settings.DEBUG:\n proxy_urls = [\n re_path(r'^__webpack_dev_server__/(?P.*)$', proxy_cra_requests),\n re_path(r'^(?P.+\\.hot-update\\.(js|json|js\\.map))$', proxy_cra_requests),\n ]\n urlpatterns.extend(proxy_urls)\n```\n\nNext, follow the instructions below that correspond to your project's version of `react-scripts`:\n\n
\n For projects using react-scripts@<3.3.0\n\n Add one more url to `proxy_urls` above:\n\n ```python\n proxy_urls = [\n # ...snip...\n re_path(r'^sockjs-node/(?P.*)$', proxy_cra_requests),\n ]\n ```\n
\n\n
\n For projects using react-scripts@>=3.3.0\n\n Create an **.env** file in the root of your Create-React-App folder with the following environment variable:\n\n ```\n WDS_SOCKET_PORT=3000\n ```\n
\n\n### 3. asset-manifest.json\n\nFinally, run CRA's `npm run build` command once to generate a `build/` directory. **django-cra-helper** requires the **build/asset-manifest.json** file contained within to help load non-JS and non-CSS assets that might be used in any React components. This command should be re-run any time a new non-JS or non-CSS asset is added to the project.\n\n## Development\n\nIf the CRA project's liveserver is started via `npm start` prior to starting Django's development server via `python manage.py runserver`, code changes in the React codebase will be updated immediately within Django views as well.\n\nWhen the CRA liveserver is running, **django-cra-helper** adds a `bundle_js` array template variable that can be inserted into the Django view's template to load the liveserver's various files containing all of the current JS and CSS. These files are recompiled on-the-fly by the liveserver whenever edits are made to the React code. This file can be added to a Django template as follows:\n\n```html\n{% if bundle_js %}\n {% for file_url in bundle_js %}\n\n {% endfor %}\n{% endif %}\n```\n> Note: Don't use the `static` template tag here! This file needs to be loaded from the CRA liveserver instead.\n\n## Production\n\n**django-cra-helper** also takes care of ensuring that Django's `collectstatic` command pulls in production-ready bundles built by CRA's `npm run build` command.\n\nFirst, prepare React files for production with the typical CRA `npm` build command:\n\n```sh\nnpm run build\n```\n\nThis will output bundled, minified JavaScript and CSS, and assets to the `/build/` folder within the CRA project folder.\n\nOnce this command is complete, run the following Django command to gather static files, including the compiled React assets:\n\n```sh\npython manage.py collectstatic --no-input\n```\n\nReact assets will be included with the other static assets in the `settings.STATIC_ROOT` directory, to be served as is usual in a Django production environment. An `asset-manifest.json` file will also get pulled in. The contents of this CRA-generated file are required by **django-cra-helper** to help reference React files that have had a unique hash added to their filenames during the build process.\n\nSimilar to the `bundle_js` template variable mentioned earlier, **django-cra-helper** includes numerous other template variables when the CRA liveserver is _not_ running:\n\n
\n For projects using react-scripts@>=3.2.0\n\n Starting with `react-scripts@3.2.0`, a new `entrypoints` property can be found in **asset-manifest.json**. This contains an array of files that **django-cra-helper** makes available in templates to more easily inject these files via new `entrypoints.css` and `entrypoints.js` arrays:\n\n ```html\n {% for file in entrypoints.css %}\n \n {% endfor %}\n ```\n ```html\n {% for file in entrypoints.js %}\n \n {% endfor %}\n ```\n\n > NOTE: These JavaScript and CSS files should be arranged in an order required for the site to load; the ultimate order is derived from the order present in **asset-manifest.json**.\n
\n\n
\n For projects using react-scripts@>=3.0.0 to react-scripts@<3.2.0\n\n Code-splitting was introduced in later versions of `react-scripts` that split up `main_js` into multiple files. Additional `\n \n \n {% endif %}\n ```\n\n The naming of `static_js_2_9a95e042_chunk_js` above will differ from project to project. Unfortunately you'll have to manually confirm this value in your project's **asset-manifest.json** and update accordingly. It doesn't seem to change between builds, though, so it may not be a value you need to regularly update...\n
\n\n
\n For older projects using react-scripts@<=2.1.8\n\n The two most important variables are `main_js` and `main_css`. These can be injected into the page via a typical call to `{% static %}` in the template:\n\n ```html\n {% if main_css %}\n \n {% endif %}\n ```\n ```html\n {% if main_js %}\n \n {% endif %}\n ```\n\n > NOTE: Recent attempts at building a fresh CRA project with `react-scripts@2.1.8` were unsuccessful in recreating SPAs that allowed for just a single `main_js`. `npm run build`-produced artifacts functioned almost identically to artifacts generated the same as `react-scripts@3.1.2`, detailed below.\n >\n > There may be child dependencies of `react-scripts` that make it no longer possible to start apps that will function with the above instructions. In these cases, please try the instructions in the next section.\n
\n\n### Supporting CRA's relative paths\n\nCRA allows developers to specify a [relative sub-folder for their site to be hosted from](https://create-react-app.dev/docs/deployment/#building-for-relative-paths) via the `\"homepage\"` property in **package.json**:\n\n```json\n{\n \"name\": \"cra-app\",\n \"version\": \"0.1.0\",\n \"homepage\": \"/frontend\",\n ...\n}\n```\n\nWhen this value is set, `npm run build` will output assets and an **asset-manifest.json** with paths prepended with the path prefix:\n\n```\nBefore: /static/js/main.319f1c51.chunk.js\nAfter: /frontend/static/js/main.319f1c51.chunk.js\n```\n\nTo make sure the React imports/assets/etc... can be found even when hosted through Django, you'll also need to update `STATIC_URL` in Django's settings.py to include the path prefix:\n\n```py\nSTATIC_URL = '/frontend/static/'\nCRA_PACKAGE_JSON_HOMEPAGE = '/frontend'\n```\n\nThe value set to `CRA_PACKAGE_JSON_HOMEPAGE` above should match the value of `\"homepage\"` in **package.json** so that **django-cra-helper** can find the CRA liveserver and redirect appropriately:\n\nOnce these changes are made then the React app should be able to find everything it needs to function.\n\n## React in Django templates\n\n### Specifying React Components via template context\n\nThe CRA project will need to undergo a small bit of re-architecture to prepare it to accept input values via context when Django serves the view. The following is an example of how a couple of small tweaks to a CRA project's **src/index.js** file will establish a simple API for Django to communicate with the bundled React codebase:\n\n```js\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\nimport './index.css';\n\n/**\n * Maintain a simple map of React components to make it easier\n * for Django to reference individual components\n */\nconst pages = {\n App,\n}\n\n/**\n * If Django hasn't injected these properties into the HTML\n * template that's loading this script then we're viewing it\n * via the create-react-app liveserver\n */\nwindow.component = window.component || 'App';\nwindow.props = window.props || { env: 'Create-React-App' };\nwindow.reactRoot = window.reactRoot || document.getElementById('root');\n\n/**\n * React the component as usual\n */\nReactDOM.render(\n React.createElement(pages[window.component], window.props),\n window.reactRoot,\n);\n```\n\nBasically, `index.js` is updated to read values set to `window.component`, `window.props`, and `window.reactRoot` and use these to render a component. Each of these three \"inputs\" will allow Django to easily specify which component to initialize on a per-view basis:\n\n* `window.component`: A **string** that points to a Component entry in `pages`\n* `window.props`: An **Object** containing props to get passed into the Component\n* `window.reactRoot`: an **instance** of `document.getElementById`\n\n> Note: Settings these values is optional. The defaults specified in the template above enable components to render as expected when viewed from the CRA liveserver.\n\nNow that the \"API\" is in place, Django Views can include values for these inputs via the context they pass to their template:\n\n```python\ndef index(request):\n context = {\n 'component': 'App',\n 'props': {\n 'env': 'Django',\n },\n }\n return render(request, 'index.html', context)\n```\n\nBelow is the Django app view's **index.html** template that can render across multiple versions of `react-scripts` (intended only for demo purposes with a fresh CRA app):\n\n```html\n{% load static %}\n\n\n\n \n \n \n \n {% if entrypoints %}\n {% for file in entrypoints.css %}\n \n {% endfor %}\n {% elif main_css %}\n \n {% endif %}\n Django + React Project\n \n\n \n
Loading...
\n\n {{ props | json_script:\"react-props\" }}\n\n \n {% if bundle_js %}\n {% for file in bundle_js %}\n \n {% endfor %}\n {% elif entrypoints %}\n {% for file in entrypoints.js %}\n \n {% endfor %}\n {% elif main_js %}\n \n {# make sure to update this accordingly according to asset-manifest.json #}\n \n \n {% endif %}\n \n\n\n```\nThe context's `component` and `props` are bound to `window.component` and `window.props` respectively.\n\nNote the use of the `json_script` filter when setting `windows.props`. [Django provides this filter](https://docs.djangoproject.com/en/3.1/ref/templates/builtins/#json-script) as a way to easily sanitize and convert a Python `dict` to a Javascript `Object`. The contents of the injected `