VSCode の Remote Container で React開発環境を構築する

VSCode の Remote Container で React を開発できればいいな、と。で、やってみたら create-react-app は激重だし、 Hot Reload もしない。 npm start, yarn start は遅い。こんなもの使いもんになるか！と思っていました。

でもポイントを押さえればローカル開発と遜色ないパフォーマンスで Hot Reload 可能な環境を作ることができます。しかもめっちゃ簡単です。ちょっと docker の知識は必要です。

# 環境

- Windows 11
- Docker Desktop for Windows ver. 4.6.1 (on WSL2)
- Visual Studio Code
- [Remote Development Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)

# 1. フォルダを作る

docker-compose を使用するので、フォルダ名が docker の images 名と関係があります。あまり適当な名前にはしないほうがいいです。プロジェクト名などがいいでしょう。

# 2. Dockerfile

作ったフォルダ直下に Dockerfile を作ります。Node.js の ver.16 をベースにします。

```Dockerfile
FROM node:16

ENTRYPOINT tail -f /dev/null
```

# 3. docker-compose.yml

同じく作ったフォルダ直下に docker-compose.yml を作ります。
services のすぐ下の react 、というのはサービス名です。好きに変えて構いませんが、この名前も image 名に関係があります。（フォルダ名_サービス名という image名になる）

```yml:docker-compose.yml
version: '3.8'
services:
  react:
    build: .
    ports:
      - 3000:3000
    volumes:
      - .:/workspace:cached
      - home:/home
volumes:
  home:
```

# 4. devcontainer.json

devcontainer.json というのは、リモート接続するための構成ファイルなのですが、手動で作成する必要はありません。
まずフォルダを VSCode で開き、画面左下の Remote アイコンをクリックします。もしアイコンがない場合はプラグインがインストールされていません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/366effca-f0c4-07a6-655e-b71e68a3c2b7.png)

インストールしましょう。
https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack

ダイアログが開くので、Add Development Container Configuration Files...を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/42b8e95f-1f85-86da-b0f7-c0e17858f0eb.png)

From 'docker-compose.yml' を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/a170a397-9a8f-4c0f-5752-2f6ab3a48eeb.png)

devcontainer.json ファイルが自動的に生成されます。このファイルの "workspaceFolder" の値を次のように編集します。この編集は必須ではありませんが、これで Remote Container に接続時に開く既定のディレクトリは /home になります。やっておいたほうがいいです。

```json:devcontainer.json
	"workspaceFolder": "/home",
```

# 5. 接続
準備完了です。Remote Container へ接続します。画面左下の Remote Container のアイコンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/37cd3834-6f63-9ee7-53ec-1f7095ec0d59.png)

Reopen in Container を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/2d3a481b-7b9c-d26b-8ea4-b2fc5af8066c.png)

しばらく待つと（最初は数分待ちます）、ステータスバーが次のように変わります。接続している状態です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/d5dd8167-f3bb-85d0-d089-ae62263ff2a2.png)

# 6. create-react-app を試す

ターミナルを開きましょう。/home が開くと思います。
早速 create-react-app で React アプリを作りましょう。私は yarn 派なので次のコマンドです。

```bash
yarn create react-app myapp --template typescript
```

ローカルで作るときと比べてそれほど変わらない時間で作成は完了すると思います。

実行します。

```bash
yarn start
```

ブラウザが立ち上がって、http://localhost:3000 でいつものように動きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/21583bd2-a784-d6c6-f418-96b304dd08dc.png)

適当にソースを変えて保存してみてください。いつものように自動的にReloadします。

```tsx
- Learn React
+ Learn React !!!!!
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/0fd3d0ca-9449-4f75-7a42-103f61ba43bc.png)

# 7. この環境を使う時の注意

## なぜ高速なのか
VSCode の Remote Container は既定でContainer 側の /workspace と、手順1で作ったフォルダを同期します。つまり、フォルダ内部に Explorer で何かファイルを作ると、Remote Container の /workspace でそれを参照・編集ができます。
これは docker の Bind Mount という機能です。この機能は大変便利ですがこのフォルダへの読み書きがすごく遅いのです。

そして既定の devcontainer.json のままの場合、remote 接続すると /workspace を既定の場所として接続します。そのため暗黙的に /workspace 配下に React アプリを作ってしまい、create-react-app が死ぬほど遅いのです。（私の環境では30分近くかかりました）

なのでこの手順では /workspace 配下ではなく、自然と /home に React プロジェクトを作るように devcontainer.json を修正しました。

node_modules を Bind Mount から外すことで高速化する手順を Web に Tips として見つけることができると思いますが、考え方はそれと同じです。しかしこの方法では Hot Reload ができないという問題が残ります。

## なぜ Hot Reload できるのか

Web上を検索すると docker container 内の React を Hot Reload するための方法がいくつか紹介されています。主に環境変数を次のように設定する、という記載が見つかると思います。

```bash
CHOKIDAR_USEPOLLING=true
```

create-react-app が ver.5になってから、この設定では Hot Reload は動かなくなりました。原因は定かではありませんが、少なくとも私が確認した限りでは /workspace 配下に作った React App は ディレクトリの Watch が上手く動かないようです。Bind Mount が影響しているのだと思われます。
この手順では /home 配下に React プロジェクトを作っていますので、設定不要で Hot Reload ができます。

## データの永続化

この手順では /home 配下は docker volume として永続化しています。（docker-compose.yml の Volumes の箇所）そのため、docker の image や container を作り直しても /home 配下のデータが消えることはありません。

もしこの環境が不要になり、 image と container を削除しても Volume は残り続けます。手動で削除が必要です。

docker volume の一覧
```bash
docker volume ls
```

docker volume の削除
```bash
docker volume rm <VOLUME NAME>
```

コンテナとの紐づけが無くなった docker volume をまとめて削除
```bash
docker volume prune
```

## ソースがホストから見えないのは困る？

Remote Container で実装したソースは GitHub などの 外部リポジトリなどへ push することがほとんどだと思いますので、ホストからソースが見えなくても困ることはないと思います。

もしかしたら build した結果をホスト側で必要とする場合があるかもしれません。その場合は build 後に 結果を /workspace へコピーすれば良いです。
package.json の scripts に 1行追加します。

```json:package.json
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "postbuild": "rm -rf /workspace/build/ && cp -r build /workspace/build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
```

追加したのは postbuild の行です。このように postbuild という名前の key でスクリプトを定義すると build 後に実行されます。（prebuild という名前の key であれば build 前に実行します）

こうすれば build 実行後に build 結果が /workspace へコピーされます。
build します。

```bash
root@362aa11b8796:/home/myapp# yarn build
yarn run v1.22.18
$ react-scripts build
Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  46.21 kB  build/static/js/main.1ef0a932.js
  1.78 kB   build/static/js/787.d3befce1.chunk.js
  541 B     build/static/css/main.073c9b0a.css

The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.
You may serve it with a static server:

  yarn global add serve
  serve -s build

Find out more about deployment here:

  https://cra.link/deployment

$ rm -rf /workspace/build/ && cp -r build /workspace/build
Done in 12.23s.
```

確認します。

```bash
root@362aa11b8796:/home/myapp# ls -al /workspace/build/
total 36
drwxr-xr-x 1 root root 4096 Apr 11 05:22 .
drwxrwxrwx 1 root root 4096 Apr 11 05:22 ..
-rw-r--r-- 1 root root  605 Apr 11 05:22 asset-manifest.json
-rw-r--r-- 1 root root 3870 Apr 11 05:22 favicon.ico
-rw-r--r-- 1 root root  644 Apr 11 05:22 index.html
-rw-r--r-- 1 root root 5347 Apr 11 05:22 logo192.png
-rw-r--r-- 1 root root 9664 Apr 11 05:22 logo512.png
-rw-r--r-- 1 root root  492 Apr 11 05:22 manifest.json
-rw-r--r-- 1 root root   67 Apr 11 05:22 robots.txt
drwxr-xr-x 1 root root 4096 Apr 11 05:22 static
```

Exploer　からも確認します。build フォルダがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/190ae256-d435-b43d-901d-2ce6dd2172f4.png)

build フォルダの中身です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/600f9b5c-7b3b-f9b8-5c1a-d56ca5f8b11e.png)

良さそうですね。