最近、私的な仕事に忙しいです。大画面の表示ページは React で作成されています(create-react-app を使用)。クライアントは、バックエンドの制御ページの URL にアクセスして、この大画面表示に直接アクセスしたいと考えています。フロントエンドとバックエンドを分離してデプロイする必要はありません。
自分で試してみましたが、いくつかの問題に遭遇しました。
create-react-app は、ビルド結果をプロジェクトのルートディレクトリの build フォルダに配置します。ビルド後のパスの構造は次のようになります。
- build
- static
- css
- style.[hash].css
- style.[hash].css.map
- js
- main.[hash].js
- main.[hash].js.map
- index.html
- [more meta files]
create-react-app によって生成された静的リソースは、static
パスの下に配置されます。たとえば、ビルド後のindex.html
でのリンクは次のようになります。
<link href="/static/css/2.0a6fdfd6.chunk.css" rel="stylesheet" />
ブラウザが解析すると、GET /static/css/2.0a6fdfd6.chunk.css
というリクエストが送信されます。
単純にindex.html
ファイルを返すルートを追加すると、以下の問題が発生します。
Flask にはstatic_folder
という概念があり、/static
パスのリクエストは設定されたstatic_folder
からファイルを読み取って返します。
index.html で要求されるリソースは、static_folder
から取得されます。したがって、ビルド後のファイルをstatic_folder
に直接配置すればよいですか?
static_folder
には、バックエンドページで必要な静的リソースが既に含まれており、css/js などのフォルダが種類ごとに作成されています。React でビルドされたリソースをstatic_folder
に直接コピーすると、異なる js ファイルが混在することになります。React は再ビルドごとに異なる js ファイルを生成するため、更新ごとに元のファイルを削除してコピーする必要があります。
解決策#
解決策を検索してみると、Stackoverflow のディスカッションを見つけました。これを適用してみて、プロセスを記録します。
ちなみに、通常、フロントエンドとバックエンドの分離デプロイでは、Nginx などのサーバーを使用して静的リソースを返すことが一般的であり、キャッシュなども容易です。
最も評価の高い回答は、すべてのルートをキャプチャし、リクエストされたパスに基づいて対応するファイルを返す方法です。
彼(および他の人々)の例:
import os
from flask import Flask, send_from_directory
app = Flask(__name__, static_folder='react_app/build')
# Serve React App
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def serve(path):
if path != "" and os.path.exists(app.static_folder + '/' + path):
return send_from_directory(app.static_folder, path)
else:
return send_from_directory(app.static_folder, 'index.html')
リクエストされたpath
に基づいて異なるフォルダからコンテンツを返す方法です。
ファイルをテストするパス => 対応するファイルを送信 => それ以外の場合は index.html を送信。
実践#
アプリケーションの関係上、静的ウェブサイトを提供するためにfront
ブループリントを使用します。パスはapp/front_api/__init__.py
にあります。
静的リソースをapp/react_app
に配置します。
次に、react_folder
変数を静的リソースのパスに設定します。
from pathlib import Path
from flask import Blueprint, send_from_directory
front = Blueprint("front", __name__, url_prefix="/front")
react_folder = (
Path(__file__).parent.parent / "react_app"
).absolute()
# Serve React App
@front.route("/", defaults={"path": ""})
@front.route("/<path:path>")
def serve(path):
if path != "" and (react_folder / path).exists():
return send_from_directory(str(react_folder), path)
else:
return send_from_directory(str(react_folder), "index.html")
フロントエンドのホームページを設定する#
ブループリントのプレフィックス機能を使用しているため、静的リソースを取得する際にはプレフィックスが必要です。
例えば、http://localhost:5000/front
にアクセスするとindex.html
が取得され、index.html
内のリソースのアドレスは/static/xxxx
のままです。ブラウザがアクセスする際には/front/static/xxxx
にアクセスする必要があります。
package.json
でhomepage
パラメータを指定することで、基本パスを指定できます。
package.json
に以下を設定します。
"homepage": "."
これにより、index.html
内のリソースのアドレスが./static/xxxx
に設定されます。ブラウザがアクセスする際には、/front
+ ./static/xxx
にアクセスされます。
これにより、ビュー関数がキャプチャされ、設定したフォルダからファイルを取得できるようになります。
バックエンドの API アドレスを設定する#
もう 1 つ注意する点があります。
フロントエンドで開発テストを行う場合、バックエンドにリクエストを送信する必要があります。したがって、リクエストを送信する際には、異なる開発環境に応じて異なる BASE_URL を設定する必要があります。
let BASE_URL;
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
BASE_URL = 'http://127.0.0.1:5000';
} else {
BASE_URL = '';
}
// リクエストを送信する際には、BASE_URL + "/front/alarm"のようにリンクを作成します
これで機能が実装されました。
最適化#
参考:https://www.jianshu.com/p/b348926fa628
作者:AricWu
ソース:简书
ビルド後のファイルを手動でバックエンドのフォルダに移動する必要があります。手動で移動するのが面倒な場合は、npm スクリプトのフックを使用して、このプロセスを自動化することができます。
npm スクリプトには、pre と post の 2 つのフックがあり、これらのフックを使用して準備作業とクリーンアップ作業を行うことができます。
build に prebuild と postbuild の 2 つのフックを追加します。
"prebuild": "rimraf ../backend/app/react_app/*",
"build": "cross-env GENERATE_SOURCEMAP=false craco build",
"postbuild": "cpx -C build/** ../backend/app/react_app/",
ビルド前にバックエンドのファイルを削除し、ビルド後に新しいファイルをコピーします。
rimrafとcpxという 2 つのクロスプラットフォームの CLI ツールパッケージを使用しています。
他にもいくつかのクロスプラットフォームの CLI ツールパッケージがあります:
awesome-nodejs-cross-platform-cli