工作と競馬2

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

Flask+React+pywebview+PyInstallerでデスクトップアプリを作成する

概要

Flask+React+pywebview+PyInstallerでデスクトップアプリを作成し、動作を確認した。




背景と目的

ある目的でFlaskとReactで簡単なWebアプリを作成してローカルで動作させていたのだが、

デスクトップアプリとして1つにまとめたい状況

が発生した。そこで、実現するための方法について整理してメモしておく。



詳細

0. 環境


1. 作成するもの、実現手段

以下の構成とする。

  • reactでフロントエンドを作成
    • ボタンを押したら、バックエンドから取得したデータを表示
  • Flaskでバックエンドを作成
    • リッスンポートは適当に8011とする
    • フロントエンドへデータを引き渡すAPIエンドポイントを1つ用意
  • pyweviewでWebブラウザ画面をデスクトップアプリウインドウ化し、reactで作成したフロントエンド部分を表示、実行
  • PyInstallerで、フロントエンド機能、バックエンド機能、およびデスクトップアプリウインドウ機能を実行ファイル化


2. 必要なライブラリのインストール

2.1 Python

CORSを許可するため、flask-corsも入れる。

python -m venv .venv
.venv/Scripts/activate
pip install flask
pip install flask-cors
pip install pywebview
pip install pyinstaller

2.2 node

axiosは、フロントエンドからバックエンドAPIを叩くために使用。

npx create-react-app frontend
cd frontend
npm install axios


3. Flask部

最低限の機能として、/helloというパスにGETを投げたら、"hello, world!"を返すようにしておく。

なお、flaskアプリケーションをスレッドで動作させるには、

use_reloader=False, threaded=Falseをつける

ということが、

https://blog.fantom.co.jp/2023/02/16/flask-signal-only-works-in-main-thread/

で記載されているので、その通り付加する。付加しないと、メインスレッドでないと動かない旨のエラーが出てしまう。 また、Reactアプリ側からアクセスするときにCORSが必要なので、CORSを有効化。

backend.py

import threading
from flask import Flask, render_template
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/hello')
def hello():
    return {"message": "Hello, world!"}

def main():
    app.run(host='0.0.0.0', port=8011, debug=True, use_reloader=False, threaded=False)

# スレッド
server = threading.Thread(target=main, daemon=True)


4. React部

create-react-appのデフォルトのソースコードを少しだけ変更。バックエンドサーバの/helloにGETを投げるclickと表示されたボタンを1つ追加。データを受け取ったら、ボタンの名前の部分に受け取った文字列を表示する。

frontend/src/App.tsx

import logo from './logo.svg';
import React, { useState } from 'react';
import axios from 'axios';
import './App.css';

function App() {
  const [data, setData] = useState("Click");

  const fetchData = () => {
    axios.get('http://127.0.0.1:8011/hello')
      .then(response => {
        setData(response.data.message);
        console.log(response.data);
      })
      .catch(error => {
        console.error('Error fetching data:', error);
      });
  };

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
        <button onClick={() => fetchData()}>{data}</button>
      </header>
    </div>
  );
}

export default App;

ビルドしておく。frontend/buildディレクトリにファイル一式が作成される。

npm run build


5. pywebview部(メイン部)

pywebviewは、メインスレッドで動作させるため、webviewを実行するスクリプトをメインスクリプトとする。 webviewの初期表示画面として、reactで作成したフロントエンドアプリfrontend/build/index.htmlを指定。

import webview
import backend

# バックエンドサーバー機能を立ち上げる
backend.server.start()

if __name__ == '__main__':

    # WebViewを起動
    window = webview.create_window('TestApp', 'frontend/build/index.html')
    webview.start()


6. 実行ファイル化

以下のコマンドで実行ファイルを作成する。--no-consoleは、実行ファイル起動時にコンソールウインドウを表示させないようにするため。また、--add-dataは、実行に必要な参照ファイルを一式含ませるために必要。今回は、frontend/buildフォルダに出力されているフロントエンドアプリファイル一式が必要なので、それを指定。reactを使わず、flaskでフロントエンドも作っている場合は、templatesフォルダなどが対象となる。

pyinstaller main.py --noconsole --add-data 'frontend/build:frontend/build'

実行後、dist/mainフォルダに実行ファイルが作成された。


7. 動作確認

dist/mainフォルダに作成された実行ファイルをダブルクリックして起動したところ、以下のように表示された。

ここで、clickボタンを押下したところ、無事バックエンドサーバーからデータを取得し、ボタンの文字列が変更された。



まとめと今後の課題

Flask+React+pywebview+PyInstallerでデスクトップアプリを作成することができた。早速、目的のアプリに適用していきたい。