これは Djangoのカレンダー | Advent Calendar 2022 - Qiita 12/6の記事です。
フロントエンドのビルドツール Vite
を使って、ReactをDjangoで動かす方法を調べたところ、awesome-viteのページに django-vite
の記載がありました。
- Integrations with Backends | vitejs/awesome-vite: ⚡️ A curated list of awesome things related to Vite.js
- MrBin99/django-vite: Integration of ViteJS in a Django project.
そこで、フロントエンドとして React + Vite 、バックエンドとして Django + Django REST framework な構成でTODOアプリを作って試してみたため、メモを残します。
なお、記事が長いため、記事ではソースコードを省略している部分が多々あります。もしソースコードの詳細を確認したい場合はGithubを参照ください。
- https://github.com/thinkAmi-sandbox/react_todo_app_with_django_vite
- https://github.com/thinkAmi-sandbox/react_todo_app_with_django_vite/pull/1
目次
- 環境
- バックエンド(Django + DRF)のセットアップ
- フロントエンドのセットアップ
- DjangoとReactを連携して、開発環境で動かす
- DjangoとReactを連携して、本番環境で動かす
- ソースコード
環境
- WSL2
- Django環境
- React 環境
- React 18.2.0
- React Router 6.4.4
- Vite 3.2.3
- TypeScript 4.6.4
なお、 django-vite
を使った場合のシステム構成は書籍「 現場で使える Django REST Framework の教科書 (Django の教科書シリーズ) | 横瀬 明仁 」のチュートリアル同様、
- Django
- トップページへのルーティング
- テンプレートのレンダリング
- Django REST framework (DRF)
- フロントエンド向けWeb API
- React + React Router + axios
となります*1。
バックエンド(Django + DRF)のセットアップ
まずはバックエンドから作成していきます。なお、ソースコードは後述の場所で公開していますので、バックエンドの作成では省略します。
モデルを含む task
アプリと、APIの api
アプリを作成します。
$ pip install django djangorestframework django-vite $ django-admin startproject config . $ python manage.py startapp api $ python manage.py startapp task
taskアプリを実装します。今回はadminサイトでデータ登録するため、 admin.py
も実装しておきます。
- models.py
- admin.py
apiアプリも実装します。
- serializers.py
- views.py
- urls.py
configディレクトリにも修正を加えます。
- urls.py
ファイルの実装が終わったところでマイグレーションを実行します。
$ python manage.py makemigrations $ python manage.py migrate
adminサイトでデータ登録するためのユーザーを作成します。
$ python manage.py createsuperuser Username (leave blank to use 'thinkami'): admin Email address: admin@example.com Password: pass12345 Password (again): pass12345 Superuser created successfully.
Djangoを起動し、adminサイトでデータを登録します。
$ python manage.py runserver # http://localhost:8000/admin/ にアクセスして、データ登録
データ登録後、curlで動作確認を行います。レスポンスがあればOKです。
$ curl http://localhost:8000/api/tasks/ [{"id":1,"content":"買い物","updated_at":"2022-11-28T20:36:19.668279+09:00"}]
フロントエンドのセットアップ
Viteを使ってReactをセットアップ
django-viteのREADMEによると、Viteを使ってReactをセットアップするところまでは、通常のVite + Reactのセットアップで良いようです。
https://github.com/MrBin99/django-vite#vitejs
そこで、Viteを使って frontend
ディレクトリの中にReactアプリをセットアップしていきます。
$ npm create vite@latest Need to install the following packages: create-vite@3.2.1 Ok to proceed? (y) y ✔ Project name: … frontend ✔ Select a framework: › React ✔ Select a variant: › TypeScript Scaffolding project in path/to/django_vite/static... Done. Now run: cd static npm install npm run dev
あとは画面にあるように、 npm install
と npm run dev
を実行した後、ターミナルに表示されたURLへアクセスして、Vite + React 画面が出ればOKです。
frontendディレクトリの整理
以下の設定ファイルを、 frontend
ディレクトリからプロジェクトルートディレクトリへと移動します。
また、以下の不要なディレクトリファイルは削除します。
- node_modules/
- public/
- index.html
整理後、ルートディレクトリで npm install
しておきます。
DjangoとReactを連携して、開発環境で動かす
ここまでで一通りセットアップできたので、DjangoとReactが連携するアプリを作っていきます。
vite.config.tsでNode.jsの path モジュールを import できるようにする
以下を参考に、Node.jsの path
モジュールを import
できるようにします。
TypeScriptのモジュールのインポートには import を使う|まくろぐ
$ npm install --save-dev @types/node
Djangoと連携できるよう vite.config.ts を設定
Viteの設定ファイルである vite.config.ts
を定義します。
plugins
- デフォルトのまま (
react()
を指定)
- デフォルトのまま (
root
- https://ja.vitejs.dev/config/shared-options.html#root
- 今回は開発環境のエントリーポイントである
main.tsx
が置いてあるディレクトリを指定resolve('./frontend/src')
base
- https://ja.vitejs.dev/config/shared-options.html#base
django-vite
の記述「Set the base options the same as your STATIC_URL Django setting.」に従い、STATIC_URL
と同じ値を設定/static/
server
- https://ja.vitejs.dev/config/server-options.html
- Viteの開発サーバ設定
server.watch
- https://ja.vitejs.dev/config/server-options.html#server-watch
- 今回はWSL2上に構築するため、
usePolling
などを設定
resolve.extensions
- disableGlobbing
- https://ja.vitejs.dev/config/shared-options.html#resolve-extensions
- TypeScriptを使う場合でもデフォルトのままで良さそうなため、今回は記述を省略
- disableGlobbing
設定ファイル全体は以下のとおりです。
import { defineConfig } from 'vite' import { resolve } from 'path' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], // 開発環境のmain.tsxが置いてある場所 root: resolve('./frontend/src'), // Djangoでの静的ファイル配信設定である STATIC_URL と同じになるよう設定 base: '/static/', server: { host: 'localhost', port: 5173, open: false, watch: { usePolling: true, disableGlobbing: false, }, }, })
Vite側と連携できるよう、Djangoの設定ファイルを修正
Djangoの設定ファイルは開発環境向けと本番環境向けとで分けます。
今回は開発環境で動作を確認してから本番環境の設定を行うことにします。
そこで、まずは開発環境向けの設定のみを行います。
開発環境向け
django-viteまわりの設定
django-viteのREADMEに従い設定します。
いくつか設定項目はありますが、開発環境向けとしては以下の項目を settings.py
に追加すれば良さそうです。
ちなみに、Viteは3系から開発サーバの起動するポートが 5173
へと変更になりましたが、django-viteでのデフォルトポートは今のところ 3000
のままのようです。
Vite 3.0 no longer defaults to port 3000 · Issue #51 · MrBin99/django-vite
そのため、 DJANGO_VITE_DEV_SERVER_PORT
の設定も必要です。
# 開発環境の場合、この値は参照されないので設定不要 # ただ、キーや中身がないと以下のエラーになるため、中身は適当な値を設定しておく # AttributeError: 'Settings' object has no attribute 'DJANGO_VITE_ASSETS_PATH' DJANGO_VITE_ASSETS_PATH = BASE_DIR # HMRするためDebugと同じにしておく DJANGO_VITE_DEV_MODE = DEBUG # Vite.jsがv3系からポートが変更になったので対応 DJANGO_VITE_DEV_SERVER_PORT = 5173
CORSの設定
開発環境では、ReactとDjangoでは別オリジンになります。
- React
- Viteのサーバ上で動かすため、ポート
5173
- Viteのサーバ上で動かすため、ポート
- Django
- デフォルトのポートで動かすため、ポート
8000
- デフォルトのポートで動かすため、ポート
そのため、 django-cors-headers
をインストールしてCORSの設定を追加します。
adamchainz/django-cors-headers: Django app for handling the server headers required for Cross-Origin Resource Sharing (CORS)
INSTALLED_APPS = [ ... 'corsheaders', ... ] MIDDLEWARE = [ ... 'corsheaders.middleware.CorsMiddleware', # 追加 'django.middleware.common.CommonMiddleware', ... ] CORS_ALLOWED_ORIGINS = ( 'http://localhost:5173', 'http://127.0.0.1:5173', )
templateで if debug をするための設定
後述しますが、templateでは開発環境のみ設定するタグやスクリプトがあります。
そのためには、Djangoのtemplateで if debug
を使うことで、開発環境のみレンダリングするようにします。
また、templateで debug
の値を使えるようにするには、
- template の
context_processors
に'django.template.context_processors.debug'
を追加 INTERNAL_IPS
で使用するIPアドレスを定義
なお、ルートディレクトリの templates
ディレクトリに React を render するテンプレートを入れておくことから、 DIRS
への追加も合わせて行っておきます。
TEMPLATES = [ { ... 'DIRS': [BASE_DIR / 'templates'], # 追加 ... 'OPTIONS': { 'context_processors': [ ... 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.debug', # 追加 ], }, }, ]
フロントエンドを実装
今回は frontend/src
ディレクトリの中に、Reactアプリを実装していきます。
ライブラリのインストール
まずは必要なライブラリとして
- React Router
- フロントエンドでのルーティングを行うため
- axios
- バックエンドとの連携を行うため
をインストールします。
$ npm install axios $ npm install react-router-dom
Reactアプリを実装
frontend/src/
ディレクトリの中に、以下の役割を持ったファイルを用意します。
なお、各ファイルの実装については、 django-vite
だからといって特別なことをしていません。
そのため、ここでは省略します。詳細は公開したソースコードを参照してください。
ファイルパス | 主な役割 |
---|---|
main.tsx | エントリポイントであり、App.tsxを読み込む |
App.tsx | pages/ 以下のコンポーネントに対するルーティングを定義 |
pages/Index.tsx | 一覧ページ |
pages/TaskDetail.tsx | 詳細ページ |
pages/TaskForm.tsx | 登録ページ |
Djangoの実装
Django template
django-viteのテンプレートタグを使う
今回は templates/index.html
の上で django-vite
のテンプレートタグ
{% load django_vite %}
{% vite_hmr_client %}
{% vite_asset '<path to your asset>' %}
を使ってReactアプリを動かすことにします。
MrBin99/django-vite: Integration of ViteJS in a Django project.
ちなみに、 vite_asset
に指定する path to your asset
は、Reactのエントリポイントのファイルを指定します。今回は main.tsx
を指定します。
また、 main.tsx
までのパス frontend/src
については、 vite.config.ts
にて root
で設定しています。
export default defineConfig({ // 開発環境のmain.tsxが置いてある場所 root: resolve('./frontend/src'), //... }
@vitejs/plugin-react can't detect preamble に対応するコードを追加
Reactの場合、 django-vite
のテンプレートタグを書いただけでは
Uncaught Error: @vitejs/plugin-react can't detect preamble. Something is wrong. See https://github.com/vitejs/vite-plugin-react/pull/11#discussion_r430879201
のようなエラーが表示されて動作しません。
そのため、 django-vite
のissueにあるコードを追加する必要があります。
Add tag to render the react-refresh script · Issue #15 · MrBin99/django-vite
なお、この問題に対するプルリクも存在しましたが、現時点ではマージされていません。
Add template tag to enable react-refresh by BrandonShar · Pull Request #53 · MrBin99/django-vite
templateのコード全体
上記の対応を踏まえたtemplateのコード全体は以下となります。
{% load django_vite %} {% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> {% if debug %} <script type="module"> import RefreshRuntime from 'http://localhost:5173/@react-refresh' RefreshRuntime.injectIntoGlobalHook(window) window.$RefreshReg$ = () => {} window.$RefreshSig$ = () => (type) => type window.__vite_plugin_react_preamble_installed__ = true </script> {% vite_hmr_client %} {% vite_asset 'main.tsx' %} {% else %} {% vite_asset 'main.tsx' %} {% endif %} <title>Django + Vite + React + TS</title> </head> <body> <div id="root"></div> </body> </html>
urls.pyへの追加
上記テンプレートをTemplateViewで描画するよう、config/urls.pyに追加します。
なお、 re_path
を使い、Djangoでルーティングできない場合はフロントエンドにルーティングを任せています。
urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('api.urls')), re_path('', TemplateView.as_view(template_name='index.html')), # 追加 ]
動作確認
DjangoとViteの開発サーバをそれぞれ起動します。
# Django python manage.py runserer # Vite npm run dev
その後、 http://localhost:8000/tasks
にアクセスするとTODOアプリが表示されます。
DjangoとReactを連携して、本番環境で動かす
開発環境での動作が確認できたので、次は本番環境で動作させてみます。
Vite側での作業
vite.config.ts にビルド設定を追加
開発環境ではReactアプリをViteの開発サーバから配信していました。
一方、 django-vite
のREADMEにあるように、本番環境ではDjangoで配信することになります。
In production mode, assets are included as standard assets (no ViteJS webserver and HMR) like default Django static files. This means that your assets must be compiled with ViteJS before.
Configuration | Usage | MrBin99/django-vite: Integration of ViteJS in a Django project.
そこで、ViteでReactアプリをビルドするための設定を vite.config.ts
に追加します。
rollupOptions.input
にて、Reactのエントリポイントのファイルを指定します。
build.rollupOptions | ビルドオプション | Vite
また、 outDir
でコンパイル後のファイルの出力先を指定します。
build.outDir | ビルドオプション | Vite
全体はこんな感じです。
export default defineConfig({ // ... build: { // コンパイル後の出力先。DJANGO_VITE_ASSET_PATHと一致させる outDir: resolve('./frontend/dist'), assetsDir: '', manifest: true, emptyOutDir: true, rollupOptions: { input: { main: resolve('./frontend/src/main.tsx'), }, output: { chunkFileNames: undefined, }, } } })
ViteでReactアプリをビルド
ビルドの設定ができたため、Viteでビルドします。
$ npm run build > static@0.0.0 build > tsc && vite build vite v3.2.4 building for production... ✓ 80 modules transformed. ../dist/manifest.json 0.21 KiB ../dist/main.3fce1f81.css 1.37 KiB / gzip: 0.71 KiB ../dist/main.f792177d.js 186.01 KiB / gzip: 61.81 KiB
すると、 build.outDir
で指定したディレクトリにファイルが出力されます。
Vite側での作業は以上です。
Djangoの設定
settings_production.py の設定
今回は config/settings_production.py
に本番環境用の設定を追加することにします。
まずは、本番環境なので以下の環境変数を設定します。
from .settings import * ALLOWED_HOSTS = [ 'localhost', '127.0.0.1', ] # 本番環境化 DEBUG = False DJANGO_VITE_DEV_MODE = False
また、 django-cors-headers
向けの設定は不要なので空配列を指定しておきます。
CORS_ALLOWED_ORIGINS = []
本番環境では collectstatic
コマンドを使って静的ファイルを収集するため、収集対象のディレクトリを設定します。
DJANGO_VITE_ASSETS_PATH
でViteでビルドした後のファイルが含まれるディレクトリ(Viteの build.outDir
で指定したディレクトリ) を指定します。
Configuration | MrBin99/django-vite: Integration of ViteJS in a Django project.
また、ビルドしたファイルを collectstatic
の収集対象とするため、 STATICFILES_DIRS
に DJANGO_VITE_ASSETS_PATH
を含めます。
STATICFILES_DIRS | 設定 | Django ドキュメント | Django
他に、 collectstatic
の結果を入れておくディレクトリも STATIC_ROOT
にて指定します。今回はルート直下の collected_static
ディレクトリに入れるようにします。
STATIC_ROOT | 設定 | Django ドキュメント | Django
ちなみに、静的ファイルに関する設定の全体像は、以下の記事の図がわかりやすかったです。
Djangoにおける静的ファイル(static file)の取り扱い - Qiita
# collectstaticの結果を格納するディレクトリ STATIC_ROOT = BASE_DIR / 'collected_static' # 本番環境の場合、build.outDir と同じパスを指定する DJANGO_VITE_ASSETS_PATH = BASE_DIR / 'frontend' / 'dist' STATICFILES_DIRS = [DJANGO_VITE_ASSETS_PATH]
本番環境でも静的ファイルをDjangoの開発サーバから配信するよう urls.py に設定
色々準備するのが手間なので、今回はDjangoの開発サーバから静的ファイルを配信することにします。
(注意) 通常想定している本番運用とは異なるため、今回の記事を参考にして環境を構築する場合は、ここの設定はあるべき姿にしてください。
settings.pyで DEBUG=True
であればDjangoの開発サーバから配信するため、今回はその設定を無理やり移植することで、 DEBUG=False
のときもDjangoの開発サーバから配信できるようにします。
ソースコードを読むと django/conf/urls/static.py
のこのあたりの実装を移植・修正すれば良さそうです。
https://github.com/django/django/blob/4.1.3/django/conf/urls/static.py#L10
なお、ルーティングの仕様によりtemplateへルーティングされるのを防ぐため、ルーティングの先頭に静的ファイルのルーティングを追加します。
static_patterns = [ re_path(r"^%s(?P<path>.*)$" % re.escape(settings.STATIC_URL.lstrip("/")), serve, {'document_root': settings.STATIC_ROOT}), ] if not settings.DEBUG else [] urlpatterns = static_patterns + [ path('admin/', admin.site.urls), path('api/', include('api.urls')), re_path('', TemplateView.as_view(template_name='index.html')), ]
collectstatic の実行
設定が終わったので、 collectstatic
を実行します。
なお、本番環境の設定ファイルを使って実行するよう --settings config.settings_production
オプションを付けて実行します。
$ python manage.py collectstatic --settings config.settings_production You have requested to collect static files at the destination location as specified in your settings: path/to/todo_app_with_django_vite/collected_static This will overwrite existing files! Are you sure you want to do this? Type 'yes' to continue, or 'no' to cancel: yes 3 static files copied to 'path/to/todo_app_with_django_vite/collected_static', 165 unmodified.
本番環境を起動して動作確認
ViteやDjangoの開発サーバを停止した上で、Djangoの本番環境を起動します。
$ python manage.py runserver --settings config.settings_production Performing system checks... System check identified no issues (0 silenced). December 06, 2022 - 22:46:40 Django version 4.1.3, using settings 'config.settings_production' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
http://localhost:8001/tasks
にアクセスすると、画面が表示されます。
React developer tools の色が青くなっており、ビルドしたReactで動いていることが分かります。
また、Djangoのログを見ると、Djangoから静的ファイルが配信されていることも分かります。
[06/Dec/2022 22:47:59] "GET /tasks HTTP/1.1" 200 422 [06/Dec/2022 22:47:59] "GET /static/main.3fce1f81.css HTTP/1.1" 200 1405 [06/Dec/2022 22:47:59] "GET /static/main.f792177d.js HTTP/1.1" 200 190476 [06/Dec/2022 22:47:59] "GET /api/tasks/ HTTP/1.1" 200 311 [06/Dec/2022 22:47:59] "GET /favicon.ico HTTP/1.1" 200 422
以上で、django-viteを使って、Djangoの template 上で React を動かすことができました。
ソースコード
Githubに上げました。
https://github.com/thinkAmi-sandbox/react_todo_app_with_django_vite
今回のプルリクはこちらです。
https://github.com/thinkAmi-sandbox/react_todo_app_with_django_vite/pull/1
*1:akiyokoさんの記事 https://akiyoko.hatenablog.jp/entry/2022/12/01/064746 によると、DRFの本は今月改訂になるようです