WXTをwin11 + WSL2 + Dockerで動かす

# やりたかったこと、経緯
- TypeScript + React + Vite で Chrome 拡張機能を作りたい
- その開発環境を WSL2 上に Docker で構築したい
- 現行の crxjs はライブラリの更新が滞っているため、wxt を使ってみたい
- しかし、wxt を Docker コンテナ上で動かす知見がない（技術的には有用でないのかも）

# この記事のセールスポイント
- 現時点のwxtをコンテナで動かすための、コンテナ設定、wxt.config.ts の設定項目を解説

# 最終成果物とその解説
## ディレクトリ構造(抜粋)

```
.
├── Dockerfile
├── docker-compose.yml
└── host-frontend-root # コンテナの/opt/frontend-container-app-rootにマウントされるディレクトリ
    └── frontend-src-root # wxtで作成したプロジェクト
        ├── .output # chromeから読み込むディレクトリ
        ├── .wxt 
        ├── assets
        ├── entrypoints # 開発で編集するファイル
        ├── package-lock.json
        ├── package.json
        ├── public
        ├── tsconfig.json
        └── wxt.config.ts # docker対応のために編集
```

## 各ファイル
```Dockerfile
# 実際には wxt の初期化を行うため、作業時にいくつかのコードをコメントアウトしている
FROM node:23.0-alpine3.19

WORKDIR /opt/frontend-container-app-root/frontend-src-root

# Chromium のインストール
RUN apk add --no-cache \
  chromium

COPY ./host-frontend-root/frontend-src-root /opt/frontend-container-app-root/frontend-src-root

RUN npm install

```

```docker-compose.yml
services:
  frontend:
    image: frontend-image
    build: .
    restart: always
    tty: true
    volumes:
      - ./host-frontend-root:/opt/frontend-container-app-root/
    environment:
      - HOST=localhost
    network_mode: host
```

```typescript:wxt.config.ts
import { defineConfig } from 'wxt';

// See https://wxt.dev/api/config.html
export default defineConfig({
  extensionApi: 'chrome',
  modules: ['@wxt-dev/module-react'],

  dev: {
    server: {
      hostname: 'localhost',
      port: 3000,
    }
  },
  runner: {
    disabled: true,
  },
  vite:() =>  ({
    server: {
      host: 'localhost',
      port: 3000,
      strictPort: true, 
      hmr: {
        port: 3000,
      }
    }
  }),
});
```

# 前提
- Windows 11
- WSL2
- 空のリポジトリが作成済み
- Docker、Docker Compose がインストール済み

# やったこと

## 初期ディレクトリ、ファイル構成
プロジェクトルートにフォルダを作成
```bash:WSL on win11
mkdir host-frontend-root
```

プロジェクトルートに Dockerfile、docker-compose.yml を作成
```bash:WSL on win11
echo "FROM node:23.0-alpine3.19" > Dockerfile
echo "services:" > docker-compose.yml
```

この時点のディレクトリ構造は下記の通り
```bash:WSL on win11
tree -I .
.
├── Dockerfile
├── README.md
├── docker-compose.yml
└── host-frontend-root

2 directories, 3 files

```

## 初期コンテナ作成
あとで使う箇所をコメントアウトしておく
```Dockerfile:Dockerfile
FROM node:23.0-alpine3.19

WORKDIR /opt/frontend-container-app-root/frontend-src-root

# Chromium のインストール
RUN apk add --no-cache \
  chromium

# COPY ./host-frontend-root/frontend-src-root /opt/frontend-container-app-root/frontend-src-root

# RUN npm install
```

```docker-compose.yml
services:
  frontend:
    image: frontend-image
    build: .
    restart: always
    tty: true
    volumes:
      - ./host-frontend-root:/opt/frontend-container-app-root/
    environment:
      - HOST=localhost
    network_mode: host
```

コンテナを起動する
```bash:WSL on win11
docker compose build --no-cache && docker compose up -d && docker compose ps
```

## wxtを使ってプロジェクトを作成
VS Code のリモートエクスプローラーで起動したコンテナに接続する

```bash:container
pwd
/opt/frontend-container-app-root
```

wxt を使ってプロジェクトを作成する
```bash:container
npx wxt@latest init frontend-src-root
```
を実行。今回はreact、npmを選んでいるが、目的に応じて変えて良い

```bash:container
npx wxt@latest init frontend-src-root
WXT 0.19.23
ℹ Initalizing new project
✔ Choose a template › react
✔ Package Manager › npm
✔ Downloading template

✨ WXT project created with the react template.

Next steps:
  1. cd frontend-src-root
  2. npm install
```

ログが示してくれた通りコマンドを実行
```bash:container
cd frontend-src-root
npm install

> wxt-react-starter@0.0.0 postinstall
> wxt prepare

WXT 0.19.23
types...
✔ Finished in 3.013 s

added 546 packages in 8m

148 packages are looking for funding
  run `npm fund` for details
```

wxt.config.ts を編集
```typescript:wxt.config.ts
import { defineConfig } from 'wxt';

// See https://wxt.dev/api/config.html
export default defineConfig({
  extensionApi: 'chrome',
  modules: ['@wxt-dev/module-react'],

  dev: {
    server: {
      hostname: 'localhost',
      port: 3000,
    }
  },
  runner: {
    disabled: true,
  },
  vite: () => ({
    server: {
      host: 'localhost',
      port: 3000,
      strictPort: true, 
      hmr: {
        port: 3000,
      }
    }
  }),
});
```

```bash:container
/opt/frontend-container-app-root/frontend-src-root # npm run dev

> wxt-react-starter@0.0.0 dev
> wxt

WXT 0.19.23

✔ Started dev server @ http://localhost:3000
ℹ Pre-rendering chrome-mv3 for development with Vite 6.0.7
✔ Built extension in 1.747 s
  ├─ .output/chrome-mv3/manifest.json               1.05 kB 
  ├─ .output/chrome-mv3/popup.html                  744 B   
  ├─ .output/chrome-mv3/background.js               19.96 kB
  ├─ .output/chrome-mv3/chunks/popup-BxzUToPe.js    8.24 kB 
  ├─ .output/chrome-mv3/content-scripts/content.js  36.77 kB
  ├─ .output/chrome-mv3/icon/128.png                3.07 kB 
  ├─ .output/chrome-mv3/icon/16.png                 559 B   
  ├─ .output/chrome-mv3/icon/32.png                 916 B   
  ├─ .output/chrome-mv3/icon/48.png                 1.33 kB 
  ├─ .output/chrome-mv3/icon/96.png                 2.37 kB 
  └─ .output/chrome-mv3/wxt.svg                     1.07 kB 
Σ Total size: 76.08 kB                                               
ℹ Load ".output/chrome-mv3" as an unpacked extension manually 
```

ホスト側でもファイル修正できるように権限を修正
```bash:WSL on win11
sudo chown -R (wslユーザー名):(wslユーザー名) frontend-src-root/
```

## chrome-mv3 を手動で読み込む
wxt.config.ts で
```typescript:wxt.config.ts
  runner: {
    disabled: true,
  },
```
としているため、chrome-mv3 を手動で読み込む必要がある。

Chrome で下記を開く  
chrome://extensions/ ＞ パッケージ化されていない拡張機能を読み込む  
\\wsl.localhost\(Ubuntuのディストリビューション)\home\(ユーザー名)\(プロジェクトルート)\host-frontend-root\frontend-src-root\.output\chrome-mv3

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3862473/85f0a8c3-c5b1-79f0-0d1f-24d8203df432.png)
上記のような初期画面が表示され、カウント機能とホットリロードが正常に動作していることを確認。

リモートエクスプローラーを閉じ、ここまでを一旦コミットする

## Dockerfile による永続化
```Dockerfile
FROM node:23.0-alpine3.19

WORKDIR /opt/frontend-container-app-root/frontend-src-root

# Chromium のインストール
RUN apk add --no-cache \
  chromium

COPY ./host-frontend-root/frontend-src-root /opt/frontend-container-app-root/frontend-src-root

RUN npm install
```

再ビルド
```bash:WSL on win11
docker compose build --no-cache && docker compose up -d && docker compose ps
```

docker コマンドでコンテナに接続して、npm run dev を実行
```bash:WSL on win11
docker compose exec frontend npm run dev
```

再びポップアップとホットリロードの動作を確認して、環境構築は完了。

# 解説
## 今回の設定値の肝
- wxt.config.tsのdevプロパティとviteプロパティでのhostname、portの値を一致させる必要がある。
- wxt.config.tsのrunnerプロパティを無効にする。
- また、hostnameの値はdocker-compose.ymlのenvironmentプロパティのHOSTにも合わせる必要がある。
  - コンテナ内のViteサーバーとホストマシンのChrome拡張機能が通信できるようにするため。
- 現時点では、docker-compose.ymlのnetwork_modeをhostにしておく必要がある(後述)。

## vite.config.tsの内容はwxt.config.tsに記述する
公式ドキュメント
https://wxt.dev/guide/essentials/config/vite.html#change-vite-config
によれば、通常vite.config.tsのdefineConfigに記述する内容を、wxt.config.tsに記述することで、Viteの設定を変更できる。

```
// wxt.config.ts
import { defineConfig } from 'wxt';

export default defineConfig({
  vite: () => ({
    // Override config here, same as `defineConfig({ ... })`
    // inside vite.config.ts files
  }),
});
```



# 未解決事項
## network_mode: host について
本来はdocker-compose.ymlでnetwork_modeをhostにしないほうが望ましいが、WebSocket関連のエラーを回避できなかったため、一時的にhostを使用している。

## Dockerfileにおけるレイヤーキャッシュの利用

ビルド速度向上のため、ホスト側からのソースコードコピー前にnpm installを実行したい。

```
# 1. package.json と lockファイルのみコピー
COPY ./host-frontend-root/frontend-src-root/package*.json ./

# 2. 先に依存関係をインストール
RUN npm install

# 3. 全ソースコードをコピー
COPY ./host-frontend-root/frontend-src-root ./
```
しかし、wxtの場合、npm installを実行すると、`npx wxt@latest init`コマンド実行時にデフォルトで用意される
```json:package.json
  "postinstall": "wxt prepare"
```
が実行される。これにより、
```
ERROR  No entrypoints found in /opt/frontend-container-app-root/frontend-src-root/entrypoints
```
というエラーが発生するため、 `RUN npm install` 前にソースコードが存在している必要がある。

回避するには`package.json`から`postinstall`を削除し、以下のように書き換える方法がある。

```
# 1. package.json と lockファイルのみコピー
COPY ./host-frontend-root/frontend-src-root/package*.json ./

# 2. 先に依存関係をインストール (キャッシュが効きやすい)
RUN npm install

# 3. 全ソースコードをコピー
COPY ./host-frontend-root/frontend-src-root ./

# postinstallで指定されているコマンドを実行
RUN npx wxt prepare
```

これでレイヤーキャッシュを活用できるが、デフォルトの`postinstall`を削除する必要があり、対応を検討中。

## wxt.config.tsのrunner: { disabled: true } について
wxt.config.ts で
```typescript:wxt.config.ts
  runner: {
    disabled: true,
  },
```
と設定しているが、本来はfalseやオプション削除が望ましい。しかし、これを削除すると下記エラーが発生する。

```
✖ Command failed after 34.1 s

 ERROR  connect ECONNREFUSED 127.0.0.1:32885

  at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1608:16)
```

chromiumPortなどの設定を試しても改善せず、一時的にrunnerを無効にして手動ロードを行っている。

# 改善できそうな箇所
- port等の設定値を.envにまとめる
- ボイラーテンプレート化