WebSerialAPI操作クラスと、あえてCDN版Vue.jsで構築するGUIとの連携 〜SPAを避けた柔軟な構成〜

## 今回の内容

[前回](https://qiita.com/asd_m/items/06697744bbf1b3a133c1)は、WebSerialAPIを用いて、Raspberry Pi Pico にインストールした MicroPython の REPL（Read-Eval-Print Loop）と通信を行いました。  
今回は、前回のグローバルスコープで記述していた JavaScript コードをある程度汎用的に使えるようクラスとして整理し、それを Vue.js で構築した GUI 側と連携させる構成に書き直します。

## WebSerialAPI＝REPL通信クラス

前回のコードをクラス化したものが以下のリンクです：  
[WebSerial.js](https://github.com/DBA-Z21A/raspico_wifi_set_repl/blob/main/web/frontend/js/WebSerial.js)

基本的には前回と同じ機能をクラス構造に置き換えたものですが、以下の改善を加えています：

- 送受信結果とシステムメッセージの分離
- 送受信時・メッセージ表示時に呼び出されるコールバック機能の追加
- REPLプロンプトが分割受信されるケースに備えて、最新100バイトをバッファし判定
- 意図せず raw REPL モードに入ってしまった場合に備えて Ctrl+B を送信する機能の追加

これにより、ある程度実用的な WebSerial＝REPL 操作クラスとして利用できる状態になったと考えています。

## なぜ「CDN版」Vue.jsを使うのか？

GUI 側の実装に入る前に、「なぜあえて SFC（Single File Component）形式ではなく CDN 版の Vue.js を使用するのか？」という点についてご説明しておきます。主な理由は次の2点です：

1. HTML全体としての構造の見通しが悪くなる  
2. 他の SSR フレームワークや CSS フレームワークとの連携がしづらくなる

１ に関しては（SFC の長所でもあるのですが）HTML・CSS・JavaScript を `.vue` ファイルに集約する構造は、ビルド前の段階で「HTMLとしての全体像」をつかみにくく、私にとっては違和感がありました。

もちろん、SFC の思想、つまり  
**「HTML も JavaScript も CSS もまとめて 1 つの .vue ファイルに記述し、最終的にどうHTMLとして出力されるかは気にしない」**  
という設計方針は、コンポーネントの独立性や保守性を高める点で非常に優れていると理解しています。  ただしこの思想は、SPA（Single Page Application）であることを前提にしすぎているようにも感じられます。

特に ２ のように、従来のhead / body構造を明確に持つHTML用の SSR フレームワークや、既存の CSS フレームワーク（例：Bootstrap, Tailwind など）との親和性が下がるという点は無視できません。

Vue.jsを用いたSSRフレームワークには Nuxt.js という強力な選択肢もあります。ただし Nuxt は構造的に SFC を前提としており、やはり従来型の SSR や既存 HTML テンプレートとの連携が難しい場面もあります。

最近ではSPA構成が肥大化しやすく、保守性や初期表示のパフォーマンスに課題を感じることも増えてきました。もちろん、かつてのようにユーザーが操作する度にページ全体をリロードするような構成に戻るべきだとは思っていませんが、（Webゲームなどを除けば）適切に SSR を活用し、サーバ側で動的に生成されたコンテンツをページ単位で分割・表示する構成の方が今後より実用的になると考えています。

そのため、今回はあえて CDN 版 Vue.js を選択し、Composition API による再利用性も考慮しつつ、他の SSR や CSS フレームワークとの共存を意識した構成としました。

## CDN版 Vue.js での GUI 実装のポイント

この方針に基づいて作成したのが [pico_repl2.html](https://github.com/DBA-Z21A/raspico_wifi_set_repl/blob/main/web/frontend/pico_repl2.html) です。JavaScript の処理は、[WebSerial.js](https://github.com/DBA-Z21A/raspico_wifi_set_repl/blob/main/web/frontend/js/WebSerial.js) と [pico_repl2.js](https://github.com/DBA-Z21A/raspico_wifi_set_repl/blob/main/web/frontend/js/pico_repl2.js) に分離しています。

機能としては前回と同様に、**WebSerialAPI を起動し、Raspberry Pi Pico の REPL に接続、バージョン情報を取得するコマンドを送信する** というシンプルなものです。

ここで注目していただきたいのが以下のコードです：

```html
<script type="importmap">
  {
    "imports": {
      "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
      "mitt": "https://cdn.jsdelivr.net/npm/mitt@3.0.1/+esm",
      "WebSerial": "./js/WebSerial.js"
    }
  }
</script>
```

一般的な CDN 版 Vue のサンプルでは以下のようなコードが使われます：

```html
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
  const { createApp, ref } = Vue;
</script>
```

しかしこの形式では `Vue` がグローバルに展開されるため、ES Modules のように  
`import { createApp, ref } from "vue";` のようなインポートができません。  

今回使用した `<script type="importmap">` タグは、JavaScript のモジュール読み込み元を定義するための仕組みで、ES Modules のインポート先をブラウザ上でマッピングできます。このタグを使って読み込むことで、後続の `type="module"` スクリプト内で以下のように `import` 文を使えるようになります：

```javascript
import { createApp, ref } from "vue";
import emitter from "mitt";
import WebSerial from "./WebSerial.js";
```

## CDN版 Vue.js でのコード再利用

`<script type="importmap">` によってモジュールを定義すれば、`<script type="module">` の中でクラスや関数を再利用可能になります。  
[pico_repl2_5.html](https://github.com/DBA-Z21A/raspico_wifi_set_repl/blob/main/web/frontend/pico_repl2_5.html) は `pico_repl2.html` と同じ機能を持ちますが、Vue.js の初期化処理を [VueWebSerial.js](https://github.com/DBA-Z21A/raspico_wifi_set_repl/blob/main/web/frontend/js/VueWebSerial.js) 内の `vueWebSerial` 関数に集約しています。

```javascript
import { createApp } from "vue";
import { vueWebSerial } from "./js/VueWebSerial.js";

const app = createApp({
  setup() {
    const {
      ConsoleLog,
      SentLog,
      ReceivedLog,
      wasConnected,
      clickStart,
      clickClose,
      clickTest,
    } = vueWebSerial();

    return {
      ConsoleLog,
      SentLog,
      ReceivedLog,
      wasConnected,
      clickStart,
      clickClose,
      clickTest,
    };
  },
});

app.mount("#formWebSerial");
```

この形式での実装には、以下のような利点があると考えています。
- SFC形式に劣らないコード再利用性を確保
- SSR や CSS フレームワークとの連携がしやすい
- （Vue.js用に特化していない、一般的な）Web開発環境の支援機能を活用可能

---

以上、同じような構成を検討されている方の参考になれば幸いです。

## 次回の予定

次回は、今回ご紹介した `VueWebSerial.js` を再利用し、REPL を通じて Raspberry Pi Pico 上で MicroPython のコードを**ブラウザから実行する**実装へと進めていきます。