カスタムタグを使った Web 版 Pyxel の動作を理解する（１）

## Pyxel は Web ブラウザで動いちゃう

Pyxel は Windows、Mac、Linux、Web という４つの動作環境に対応しています。
特に異色なのは Web でしょう。他の３つは要するに OS 上にインストールした Python 環境であるのに対し、Web はつまり Web ブラウザで動くということです。

また、Web ブラウザというのは、世の中の悪い人たちからの攻撃を防ぐため、コンピュータ上のファイルアクセスが制限されているなど、様々な制約もあります。

そのような環境で、どのように Pyxel が動作しているのか。  
また、 Web 版特有の留意点などを調べてみたいと思います。

## なぜこのような調査をしようと思ったのか

[サムライアプス](https://www.samurai-apps.com/)という団体名で、Pyxel を使った写経スタイルのプログラミング教育ウェブサービス「Code & Magic」（コードアンドマジック）を開発しています。

https://codeandmagic.samurai-apps.com/

本記事は、このサービスの開発に際して調査したことのまとめとなります。

## ３種類の利用方法

まずドキュメントのおさらいとなりますが、 Web 版 Pyxel の利用方法には、次の３種類があります。

> * Pyxel Web Launcher に GitHub リポジトリを指定する
>    Pyxel Web Launcher の URL に GitHub のリポジトリ名を指定すると、指定したリポジトリを直接読み込み、Web ブラウザ上でアプリを実行できます。アプリを GitHub で公開している場合、最も簡単な実行方法です。
> * Pyxel アプリを HTML ファイルに変換する
>   アプリが Pyxel アプリケーション形式 (.pyxapp) になっている場合は、pyxel app2htmlコマンドを使って HTML ファイルに変換できます。変換後の HTML ファイルはサーバーを必要とせず、単体で実行可能です。
> * Pyxel カスタムタグを使って HTML ファイルを作成する
>   Pyxel 専用のカスタムタグを使用して、アプリ実行用の HTML ファイルを作成します。作成した HTML ファイルはサーバーでホスティングする必要がありますが、既存の HTML ページへの組み込みやカスタマイズが可能です。

各利用方法にはさらにコマンドを指定でき、Python スクリプト（`.py`）から Pyxel アプリの実行、 Pyxel アプリケーション形式（`.pyxapp`）ファイルからの Pyxel アプリの実行、 Pyxel Editor の起動、の３種類のいずれかとなります。  
対応をまとめると下記となります。

|    | `.py`から Pyxel アプリの実行 |  `.pyxapp`から Pyxel アプリの実行 | Pyxel Editor の起動 |
| -- | -- | -- | -- |
| Pyxel Web Launcher で GitHub レポ指定 | `run` | `play` | `edit` |
| Pyxel アプリを HTML ファイルに変換 | &mdash; | 対応 | &mdash; |
| カスタムタグを使った HTML で実行 | `pyxel-run` | `pyxel-play` | `pyxel-edit` |

> * `run`: Python スクリプトを実行する (別ファイルやディレクトリのインポートは不可)
> * `play`: Pyxel アプリを実行する
> * `edit`: Pyxel Editor を起動する

実装を推定するに、上の２つはカスタムタグを使った方法を自動的にジェネってくれるものとみていいでしょう。

本記事では、このカスタムタグを使った方法について深堀りしていきます。

## カスタムタグを使うには

本節もドキュメントの振り返りです。

カスタムタグ（カスタム要素）というのは、独自の HTML を作ることができるという [HTML の機能](https://developer.mozilla.org/ja/docs/Web/API/Web_components/Using_custom_elements)です。

Pyxel は、 Web サイト埋め込みが便利になるようなカスタムタグを用意してくれているのですね。  
ネットコミュニティで共有する文化にとてもフォーカスしていることの表れだと感じます。

使い方は簡単です。  
まず、HTMLに下記のスクリプトタグを追加しましょう。

```
<script src="https://cdn.jsdelivr.net/gh/kitao/pyxel/wasm/pyxel.js"></script>
```

以上で、次の３つの Pyxel カスタムタグが使えるようになります。

* `<pyxel-run>`
* `<pyxel-play>`
* `<pyxel-edit>`

これらのタグをあなたの Web サイトに追加することで、 Pyxel アプリケーションや Pyxel Editor をページの中に埋め込むことができてしまいます。

### ファイルは Web サーバーから配信されている必要がある

原則として実行したいスクリプトファイル（`.py`）や Pyxel アプリケーション（`.pyxapp`）、 Pyxel リソースファイル（`.pyxres`）は、 Web サイトと同じ Web サーバー上から配信されている必要がある点に注意しましょう。（１つだけ例外あり、後述。）

この制限は、セキュリティ上の理由による Web のルールによるもので、ルール自体を回避することはできません。

たとえば、ローカルマシンにある HTML ファイルを直接ブラウザで開くことはできますが、その HTML ファイルに埋め込まれた Pyxel アプリケーションは実行されません。

### ローカルに簡易な Web サーバーを立てる

この場合、ローカルに簡易な Web サーバーを立ててしまえば解決します。例えば次のようにします。

HTML ファイルを置いたディレクトリをカレントにして、 Python 内蔵の Web サーバーを実行します。
```
python3 -m http.server
```

そして、ブラウザで、下記の URL で開けば OK です。

```
http://localhost:8000/
```

なお、勘違いする方はいらっしゃらないと思いますが、この方法でサーバーを立てたからといってインターネット経由で全世界から参照できるようにはなりませんのでご安心ください。

## `pyxel-run` による Python スクリプトの実行

ここからは各カスタムタグの具体的な使い方と、その裏側についてみていきます。

最初は `pyxel-run` タグです。

このタグは、 Python スクリプト、すなわち `.py` ファイルを読み込んで、 Pyxel アプリケーションを実行します。

Python スクリプトを読み込む方法には２種類あります。

* 外部ファイルを読み込む方法
* コードを直接埋め込む方法

### 外部ファイルを読み込む方法

`<pyxel-run>` タグの基本の使い方は Web サーバー上にある Python スクリプトのファイル名を指定する方法です。

たとえば、 `https://example.com/` 内の Web ページに、 `https://example.com/game/test.py` として公開されているファイルを埋め込む場合は、次のようなタグを記述します。

```
<pyxel-run root="/game" name="test.py"></pyxel-run>
```

ページに埋め込まれた Pyxel の挙動としては、次のような順になるようです。

* (1) Pyxel 実行環境の読み込みと初期化（Pyodideも含む）
* (2) CLICK TO PLAY を表示してユーザー入力を待つ
* (3) Python スクリプト（`.py`）の読み込み
* (4) Pyxel アプリケーションの実行
* (5) Pyxel リソースファイル（`.pyxres`）の読み込み
* (6) Pyxel パレットファイル（`.pyxpal`）の読み込み

ページ読み込み時点で (1) が発動し、 (2) の状態でユーザー入力待ちで停止します。
この過程は、読み込むファイルサイズも処理量もあるようで、早くても数秒、キャッシュされていなければネット環境やマシン能力によっては 10 秒以上かかるでしょう。

CLICK TO PLAY が表示された後、画面上で何らかのボタンを押すと、(3) と (4) へ進み Pyxel アプリケーションが実行されます。
(3) の Python スクリプトの読み込みが、 CLICK TO PLAY 時点までに終わっているわけではなく、ユーザー入力の後である点はちょっとした留意事項です。
Python スクリプトファイルが読み込めない場合はエラーとなります。

(5) の Pyxel リソースファイルは、 `load()` 命令がプログラム中で実行された時点で読み込まれます。こちらも、指定されたファイルがファイルが読み込めない場合はエラーとなります。`load()` がなければ読み込みは行われません。

(6) の Pyxel パレットファイルは、リソースファイルの読み込みがあった場合、引き続いて自動的に読み込まれます。ただし、ファイルが読み込めなくてもエラーになりません。読み込みしようとしたことは Web ブラウザの開発者コンソールにはエラーログが表示されているのでわかります。

### コードを直接埋め込む方法

`<pyxel-run>` タグのもう一つの使い方は、直接 Python スクリプトを埋め込む方法です。

以下、公式ドキュメントの例そのものですが、下記のような感じです。
```
<pyxel-run
  script="
import pyxel
pyxel.init(200, 150)
pyxel.cls(8)
pyxel.line(20, 20, 180, 130, 7)
pyxel.show()
"
></pyxel-run>
```

HTML だけで完結し、手軽であるのが一番の利点です。

流れは外部ファイルの時とほぼ同じで、 (3) が外部ファイルから読み込む代わりに HTML に埋め込まれたものを直接実行するという感じになります。

リソースファイル、パレットファイルも読み込めます。

一方、長いプログラムの埋め込みは HTML としての可読性を著しく落としてしまいます。 React や Vue などを使っている場合は変数などに分けてうまく管理するとよいでしょう。

外部モジュールの読み込みはできませんでした。複数ファイルからなるスクリプトは実行できないようです。小さなプログラム向けの方法といえそうです。

なお、この方法は、唯一 Web サーバーを立てなくても実行できる方法になります。
その場合、リソースファイルなどが読み込めません。 Python で簡単にサーバーが立てられますので、積極的に選ぶ必要はないと思います。

## `pyxel-play` による Pyxel アプリケーションの実行

次は `<pyxel-play>` タグを使って、 Pyxel アプリケーション形式（`.pyxapp`）ファイルを実行する方法です。

指定するファイルが `.pyxapp` になる以外には、基本的な使い方は `<pyxel-run>` の外部ファイルを読み込む方法と同じです。

CLICK TO PLAY の後でファイルが読み込まれる点も同じです。

ただし、 `.pyxapp` の場合は、リソースやパレットもこのファイルの中に含まれているので、 `load()` による追加の読み込み過程 (5) と (6) は発生しません。たった１つのファイルを公開すればよいということです。便利ですね。

さらに、`.py` ファイルも複数個にできます。つまり、ファイル分割が可能です。大きなアプリケーションを開発する場合や、これまでの資産を再利用して開発する場合は、ファイル分割は必須なので、`.pyxapp` 形式が必然的に必要となってきます。

### `.pyxapp` ファイルについて

`.pyxapp` の正体は、アプリケーションに必要なファイルを Zip したものです。

* `app_name/.pyxapp_startup_script` エントリーポイントとなる Python スクリプト
* `app_name/~~.py` 追加で必要な Python ファイル（省略可能）
* `app_name/~~.pyxres` Pyxel リソースファイル（省略可能）
* `app_name/~~.pyxpal` Pyxel パレットファイル（省略可能）

`pyxel package` コマンドを使うことになっていますが、上記の仕様を守れば普通の `zip` コマンドや、 Python の `zipfile` パッケージでも作成することができます。


### ファイル読み込み相手を制限できるか

ファイル読み込みには HTTP の GET メソッドで行われますが、たとえばトークンを付けるなどして、認証などを挟むことはできないようです。
そのため、読み込むことができるユーザーを限定するには工夫が必要になります。

参照元のIPアドレス制限が思いつきますが、グローバルIPアドレスを持つ相手でなければならず、特定少数としてもちょっと使いにくいですね。

もう一つは、AWS 限定ですが、 [S3 の署名付き URL を使用したオブジェクトの共有機能](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html)を利用して、期限付きのワンタイム URL を都度生成して公開する方法です。
生成した URL を連絡する方法があれば、こちらは不特定多数であっても対応はできそうです。

なお、 Location ヘッダによる 302 リダイレクトは追従してくれました。

## Web 版で利用できるパッケージには制限がある

Web 版の Pyxel は、 Web ブラウザ上で Python を動かす Pyodide というソフトウェアを利用して作成されています。
Pyodide は Web Assembly で書かれた Python 環境です。

ですので、もともと Python のみで書かれた（Pure Python の）パッケージか、バイナリ部分まで Pyodide 上に移植されたパッケージでなければ実行できません。

基本的に、 [Pyodide のリスト](https://pyodide.org/en/stable/usage/packages-in-pyodide.html)にあるものしか利用できないと考えておくとよいでしょう。

また、独自に作ったパッケージは、 Pure Python であっても読み込ませることはできないため、前節の通り `.pyxapp` を使ってファイル分割で対応すればよいですね。

## `pyxel-edit` について

最後は Pyxel Editor を起動できる `<pyxel-edit>` ですが、ここまでで長くなってしまいましたので、別記事にしたいと思います！

Pyxel なのに画像が一つもない記事になってしまってすみません。。

これにて御免！