野声

Hey, 野声!

谁有天大力气可以拎着自己飞呀
twitter
github

使用 Flask 加载打包后的 React 前端ページ

最近、私的な仕事に忙しいです。大画面の表示ページは 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からファイルを読み取って返します。

Mermaid Loading...

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.jsonhomepageパラメータを指定することで、基本パスを指定できます。
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/",

ビルド前にバックエンドのファイルを削除し、ビルド後に新しいファイルをコピーします。

rimrafcpxという 2 つのクロスプラットフォームの CLI ツールパッケージを使用しています。

他にもいくつかのクロスプラットフォームの CLI ツールパッケージがあります:
awesome-nodejs-cross-platform-cli

参考リンク#

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。