Webpackその他

メモ記事とはいえなかなか書いてる時間がとれず一月以上の間が空いてしまった。FastAPIによるバックエンド実装からTypescriptとSASSによるフロントエンド実装の勉強に入った所、WebAssemblyとその有力言語のRustに興味が写ってしまい、そのあたりを勉強しながらフロントエンドに組み込む技術を調査していたら、Webpackという技術に突き当たった。

WebAssemblyとRustに関しては別の機会に書くとして、今回はWebpack。いままで何度か単語程度に見かけはしたが、なんとなくお腹一杯感でスルーしていたが、必要に応じて調べてみると目からウロコ的な技術だった。

従来の静的コンテンツ(ブラウザ側で処理をする所謂フロントエンド)は、HTMLベースでスタールシート(CSS)やJavascriptを必要に応じて読み込む形式だったが、Webpack化することで、大まかには一つのJavascriptにまとめられる。

例えば下記のようなindex.htmlでは

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <!-- 省略 -->
  <style type="text/css">
    @import "/static/css/bootstrap-custom.css";
  </style>
  <script type="module">
    import { Alert, Button, Carousel, Collapse, Dropdown, Modal, Offcanvas, Popover, ScrollSpy, Tab, Toast, Tooltip } from "/static/js/bootstrap.esm.js"
  </script>
</head>
<body class="base">
  <script type="module">
    import {init} from "/static/js/app.js"
    window.addEventListener("load", init());
  </script>
</body>
</html>

6行目〜でbootstrapのカスタムスタイルシート(これは別途SASSでコンパイルしている)を読み込んで、その下9行目〜でbootstrap用のスクリプトをモジュールとして読み込んでいる。Bodyタグを読み込んだあとで14行目〜でシングルページアプリケーション用スクリプトapp.jsを読み込んでページロード完了後にinitを実行(window.addEventListener)。app.jsもTypescriptのソースから別途コンパイルしておりアプリケーションの規模によっては多くのモジュールに分かれてロードされる。

こういった構成のものが、Webpack化することで下記のように出来る

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <!-- 省略 -->
    <script src="/static/dist/main.js"></script>
</head>
<body class="base">
</body>
</html>

6行目のmain.js を読み込むだけとなる。main.jsはWebpackによってコンパイルされたjavascriptで読み込むべきリソースが全てパック化された生成物となる。ソースファイルは一般にindex.jsとされ、下記のような内容となる

import '../scss/bootstrap-custom.scss';
import 'bootstrap/dist/js/bootstrap.esm.js';

import {init, build} from '../ts/app';

init();

window.addEventListener("load", () => {
  build();
});

一行目でbootstrapのカスタムスタイルシートを読み込むが、ここではscssのソースファイルを直接インポートして、WebpackのプラグインでSassコンパイラを起動する。生成したスタイルシートをインポートすると呼び出し側のHTMLのHEADタグ内にSTYLEタグを生成して直接コーディングするため、CSSファイルは生成されない。

2行目はbootstrap用のスクリプトをインポート。4行目でシングルページアプリケーションスクリプトを読み込むが、これもTypescriptのソースを直接していして内部でコンパイラを起動している。また、従来構成と違って読み込んだ時点ではBODYタグが生成されていないため、DOM関連の初期化があると失敗する。DOM関連の初期化はloadイベントをトリガとして始めるよう工夫が必要。

とりあえず、Webpackを導入するとこうなるというだけのメモで、Webpackの具体的な設定などはまた別の機会に。

FastAPI認証機構の追加

小規模djangoページの置き替えの為にFastAPIアプリケーションの勉強と実装実験を行っているが、次に用意したものは管理ページなどへアクセスするためのユーザー認証機構。前述の通りFastAPIはAPIの提供に特化したシンプルなフレームワークなので実用的な機能などは自前で準備する必要がある。かといって何も情報が無いかと言えばそうでも無く、公式のドキュメントやチュートリアルに必要そうなものが一通りまとめてあるので(例えばセキュリティの項目)、それをチマチマ実装していくという実に勉強になる仕組みが用意されている。

ドキュメント・チュートリアルで今回参考(コピペ)にしたのもは主に次の二点

  • セキュリティ
    • パスワード(およびハッシュ化)によるOAuth2、JWTトークンによるBearer
    • ユーザー認証の基本的な構造サンプル
  • SQL database(データベースアクセス)
    • SQLAlchemyを使用してFastAPIのパス構文に接続する方法

以上を組み合わせてユーザー情報をデータベースに保存し、ユーザー認証を行うような仕組みが実現出来る。

ただし、この段階ではAPIとしての実装なので実際に使用できるようにするには、フロントエンドの実装が必要となる。Djangoではバックエンド寄りの実装でHTMLテンプレートを用いてのUIだったが、今回のUIはFastAPI対応ではせっかくなのでシングルページの実装としてjavascript(Typescript)での実装としている。

こんな感じのログイン画面を準備してみました。ユーザー追加の画面や実際の管理画面は未実装だが、同じようなノリで追々追加していきます。

あと、このままではトークンの期限が切れる度に再認証になるので、セッションIDによるリフレッシュトークンの実装で同じ端末からはなるべくログインし続けられるような機構の導入を検討している(実装の手段はともかく見た目くらいはDjangoと同じくらいにしたいw)。

bootstrapをほぼ素で利用しているだけで何となく見た目だけでも良い感じなのが嬉しいですね。

FastAPI環境構築

前回の投稿にてDjangoの置き替えとしてFastAPIの勉強を始めてかなり時間が経ってしまいましたが、まずはDjangoの時と同じようにシングルページWEBアプリケーションのテンプレートのような形で勉強しようと思い、同様にDockerの環境構築からの覚え書きです。

  • PythonイメージベースのDockerfile
    • nodejsをインストールして、モジュールとしてTypescript、Bootstrap、SASSを追加
    • Pythonライブラリとしてfastapi関連ライブラリをインストール
    • 実行ユーザーappを作成してsudo権限を与える
  • nginxプロキシを追加したdocker-compose.yml
    • /codeをマウントしてここにアプリを配置する
    • startupスクリプトを実行させる(環境変数設定とtypescript、sassコンパイラの常駐設定、APIサーバー:uvicornのデーモン起動)

上記プロジェクトを用いてdocker-compose up -dすると、サーバーが起動してブラウザからhttp://localhost:8080/でアクセス出来るようになります。

WEBアプリケーションの実体はapp/main.pyです。サンプルではある程度サンプルページが表示されるまで作り込んでしまっていますが、覚え書きとして一番単純なアプリケーションから

from fastapi import FastAPI

app = FastAPI()

@app.get('/')
async def index():
    return {'detail': 'Hello FastAPI World.'}

として、保存。ブラウザからhttp://localhost:8080/で読み込むと、

一番単純なFastAPIアプリ

こんな感じで表示されます。

app/main.pyの1行目でライブラリを読み込んで、アプリケーションインタンスの生成(3行目)、5行目はHTTPリクエストメソッドを表す関数デコレータです。例ではルートパスに対するGETリクエスト。その下のdefが関数定義で、名前は分かりやすければ何でも良いです。この関数の戻り値がHTTPのレスポンスとしてブラウザに返されます。

見て分かるように、このアプリケーションで返されるレスポンスはHTMLではないため、結果の見た目が変です。

FastAPIはDjangoと異なりAPIが主体なのでデフォルトではJSONで返すのが基本のようです。Ajaxなどでデータのやりとりをするのに向いている感じ。

HTTP通信を使用したアプリケーションでデータのやりとりを行い、クライアント側で見た目を準備するような用途ではこのままで良さそうですが、普通のWEBアプリのようにブラウザから利用したい場合はひと工夫が必要です。

少し調べた感じでは、二通りの方法があるようです。

  1. Jinja2Templatesテンプレートライブラリを利用する
    • FastAPIのもととなるStarletteやFlaskでも使用されており、Djangoのテンプレートと同様に変数埋込で動的なHTML文書を返すことが出来る。
  2. スタティックマウントしたディレクトリでhtmlを有効にする
    • from astapi.staticfiles import StaticFiles
    • app.mount(“/html”, StaticFiles(directory=”/code/html”, html=True))
    • 上記のように追加することで http://localhost:8080/html/ で/code/htmlに保存されたindex.htmlが表示されるのでそのページに仕込んだjavascriptからFastAPIのAPIを利用することで、シングルページアプリケーションを実現することが出来る。

サンプルでは1の方法を採用して、ルートページを表示させるようにしている。2の方法をにするには@app.get(‘/’)の部分を他のパスに変更するか、レスポンスごと削除し、スタティックマウント先を’/’ルートにすると良い。

サンプルのようにHTMLテンプレートを利用したレスポンスがこちら。

Bootstrapでチョッピリレイアウトしてみた。

最初は2の方法が良いなと思ってみたけど、サンプルにあるようにエラーベージをHTMLでちゃんと表示させるにはテンプレートを使用した方が便利だし、どうせテンプレート使うならルートページもテンプレートで行こう!という結論となりました。