【遅い&権限が引っかかる罠に注意】Windows(WSL2), docker-composeを用いてNuxt/Nextのプロジェクトを立ち上げる

この記事は「[株式会社オープンストリーム "小ネタ" Advent Calendar 2020](https://qiita.com/advent-calendar/2020/opst-mini)」の1日目の記事です。

## はじめに

ここ最近のWebページを見るとVueやReactがいろいろなところで導入されてきて「モダンで仮想DOMなフレームワークを触りたい！」と考える方もいるでしょう。
(WebページからVueやReactの雰囲気を感じ取れない場合は[Wappalyzer](https://www.wappalyzer.com/)でも導入しておきましょう)

この記事ではWindowsにNode.jsをインストールせず、プロジェクトごとにNode.jsのバージョンを指定してほかの環境で再現できるようにDockerを用いてプロジェクトを立ち上げます。

## 環境

 * Windows 10 Home 20H2
 * Windows 10 Pro 1909

最低限WSL2が動作する環境が必要です。

## WSL(Windows Subsystem for Linux)2の導入

調べると **公式のドキュメント含めて** 同様な方法が出ます。私がまとめたものはこちらになります。

https://qiita.com/ysd_marrrr/items/a9370c4c88b5da094194#wslwindows-subsystem-for-linux2%E3%81%AE%E5%B0%8E%E5%85%A5

次の手順でWSL2のUbuntuからDockerを操作できる環境ができ上がります。

1. WSL2を導入する
2. **Docker Desktop for Windows**を導入する


### Windows Terminal向けの設定

Windows Terminalを使いたい場合はWSL2での作業が遅くなる問題を回避するため、Windows Terminalの初期ディレクトリの設定も変えておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216963/66d5043f-9f24-19f0-137a-d873650c446f.png)


Windows Terminalの新規タブを開くボタンの横に選択肢がありますので、その「設定」から `settings.json` を開いて、Ubuntuがある `profiles` について `startingDirectory` の設定を `//wsl$/Ubuntu/home/%USERNAME%` にしましょう。

Linuxのパス (`/home/`) では受け付けられません。

```json:settings.json
...
    "profiles":
    {
        "list":
        [
            {
                "guid": "{2c4de342-38b7-51cf-b940-2309a097f518}", // 環境によって違うので触らない
                "hidden": false,
                "name": "Ubuntu",
                "source": "Windows.Terminal.Wsl",
                "startingDirectory": "//wsl$/Ubuntu/home/%USERNAME%" // ここを追加する
            }
        ]
    },
...

```

Windows エクスプローラーからのアクセスが面倒？ 
`//wsl$/Ubuntu/home/` となっているようにエクスプローラーからは `\\wsl$\Ubuntu\home\` でファイル共有のようにアクセスできます。

そして、このディレクトリをエクスプローラーのクイックアクセスに登録すれば使い勝手がよくなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216963/b1bfe8e4-3ac4-d433-4c59-804e28cf3070.png)


## Nuxt/Nextを導入する

Dockerコンテナーの立ち上げを簡単にするため、docker-composeを用います。
`docker-compose.yml` を配置する必要がありますが、 `create-nuxt-app`, `create-next-app` は空のディレクトリしか受け付けないため、別にディレクトリを作成してDockerのVolumeに割り当てる必要があります。

~~あ、ここの部分はあまりWindows関係ないです~~

### VueユーザーはNuxt

先にDockerfileで `create-nuxt-app` をグローバルにインストールするように指示してコンテナーを作らないと、Nuxtのプロジェクト作成に失敗します。

```bash
$ docker-compose exec frontend npx create-nuxt-app .
internal/modules/cjs/loader.js:883
  throw err;
  ^

Error: Cannot find module '/root/.npm/_npx/17/lib/node_modules/create-nuxt-app/node_modules/ejs/postinstall.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:880:15)
    at Function.Module._load (internal/modules/cjs/loader.js:725:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! ejs@2.7.4 postinstall: `node ./postinstall.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the ejs@2.7.4 postinstall script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2020-12-01T02_01_08_277Z-debug.log
Install for [ 'create-nuxt-app@latest' ] failed with code 1
```

そのため、ファイル構成は次の通りになります。

```bash
$ tree

├── Dockerfile
├── docker-compose.yml
├── .dockerignore
└── src <--- ここにNuxtのプロジェクトを作る
```

docker-compose, Dockerfileの設定内容は次の通りです。

 * コンテナーを立ち上げた後、追加でコマンドを実行できるように `tty:true` を追加する
 * development用のサーバーにアクセスできるようにポートを開く

```yml:docker-compose.yml
version: '3'

services:
  frontend:
    build:
      context: ./
      dockerfile: Dockerfile
    container_name: nuxt-frontend
    tty: true
    volumes: 
      - ./src:/app
    ports:
      - 3000:3000
    working_dir: /app
```

 * `create-nuxt-app` をグローバルにインストールする

```dockerfile:Dockerfile
FROM node:lts

RUN npm install -g create-nuxt-app
```

また、 `volumes` でディレクトリと紐づけている構成になっていますが、すでにプロジェクトができている状態でコンテナーをビルドしようとすると `context` で指定しているディレクトリ以下をDockerデーモンにコピーするため時間がかかります。
そのため、 `.dockerignore` でプロジェクトのディレクトリを除外しておきます。

```text:.dockerignore
src
```

コンテナーのビルドが伴うため、初回起動時や`Dockerfile`を変更したときは `--build` オプションをつけます。

```bash
docker-compose up -d --build frontend
```

そのあとにコンテナー内でコマンドを実行してプロジェクトを作成します。
引数に `.` を選択して、 `docker-compose.yml` で指定した `working_dir` 直下にプロジェクトのファイルが展開されるようにします。

```bash
docker-compose exec frontend npx create-nuxt-app .
```

あとは、 `src` ディレクトリをVSCodeなどで開いたりdevelopment用のサーバーを立ち上げたりすることができます。

```bash
code src
docker-compose exec frontend yarn dev
```

そのままではWindowsホストからdevelopment用のサーバーに接続できないため、 `nuxt.config.js` にサーバーを `0.0.0.0` で開くように追記します。
設定が正しいと、 `docker-compose exec` でも `yarn dev` したときにWindowsホストのブラウザが自動的に開きます。

```js:nuxt.config.js
export default {
  server: {
    port: 3000,
    host: '0.0.0.0',
  },
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216963/17a55efc-37de-816f-f284-65872ce8dd7a.png)


プロジェクトにファイルを保存できない場合は後述する対策を試してください。

### ReactユーザーはNext

Next.jsのプロジェクトを作る場合は、Node.jsのコンテナーで `npx create-next-app` を実行するだけです。事前にコンテナーに仕込むものはありません。

そのため、ファイル構成は次の通りになります。


```bash
$ tree

├── docker-compose.yml
└── src <--- ここにNextのプロジェクトを作る
```

```yml:docker-compose.yml
version: '3'

services:
  frontend:
    image: node:lts
    container_name: next-frontend
    tty: true
    volumes: 
      - ./src:/app
    ports:
      - 3000:3000
    working_dir: /app
```

docker-compose でコンテナーを立ち上げてから、コマンドを実行してプロジェクトを作成します。
引数に `.` を選択して、 `docker-compose.yml` で指定した `working_dir` 直下にプロジェクトのファイルが展開されるようにします。

```bash
docker-compose up -d frontend
docker-compose exec frontend npx create-next-app .
```

あとは、 `src` ディレクトリをVSCodeなどで開いたりdevelopment用のサーバーを立ち上げたりすることができます。
こちらは特に設定変更しなくてもdevelopment用のサーバーに接続できます。

```bash
code src
docker-compose exec frontend yarn dev
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216963/27838a6a-31f9-a87c-71cc-303eecd1fa87.png)


プロジェクトにファイルを保存できない場合は後述する対策を試してください。

## 罠1: Windowsファイルシステムではフロントエンドのコンパイルが遅い

Windows Terminalの環境構築例で `startingDirectory` の設定を `\\wsl$/Ubuntu/home/%USERNAME%` にすべきとありますが、その理由がこちらになります。

デフォルトのままUbuntuのタブを開くとWindowsファイルシステムがカレントディレクトリになります。 `/mnt/c/` になっているやつですね。
**WSL2のUbuntuからアクセスすると厄介で、アクセスが非常に遅くなります。** "WSL2 slow" で検索するとさんざん報告されています。

そのため、WSL上で作業する場合はLinuxのファイルシステム (`/mnt/c` のようにWindowsのドライブレターが含まれない)に移動しましょう。

試しに [Nuxt.js製の簡単なポートフォリオ](https://github.com/ysd-marrrr/portfolio) のプロジェクトを `yarn install` して `yarn generate` で静的ファイルにビルドするまでの時間を確かめてみましょう。


```bash:WSLのファイルシステム(/home)の場合
$ /usr/bin/time -p yarn install

Done in 33.09s.
real 33.50
user 26.22
sys 21.91

$ /usr/bin/time -p yarn generate

Done in 7.67s.
real 7.92
user 14.26
sys 0.60
```

```bash:Windowsファイルシステム(/mnt/c/Users/)の場合
$ /usr/bin/time -p yarn install

Done in 304.11s.
real 304.39
user 71.74
sys 117.38

$ /usr/bin/time -p yarn generate

Done in 158.66s.
real 158.95
user 33.10
sys 11.48
```

Windowsファイルシステムで作業してしまうと驚きの遅さ & PCのファンが思いっきり回ります。
~~処理中にAdvent Calendarの記事だって書けます。~~

## 罠2: プロジェクト作成後に権限が引っかかってファイルの編集できない

こちらはUbuntu(Linux)でDockerを動かした環境での問題になります。

`create-nuxt-app` などで立ち上げたプロジェクトを編集しようとすると権限の問題で保存に失敗する問題です。

> Failed to save 'nuxt.config.js': Unable to write file 'vscode-remote://wsl+ubuntu/home/yoshida-m/frontend/src/nuxt.config.js' (NoPermissions (FileSystemError): Error: EACCES: permission denied, open '/home/yoshida-m/frontend/src/nuxt.config.js')

これはDockerコンテナーからホストのディレクトリに紐づけているボリュームに対して書き込みをするとrootユーザーの所有権で作成されるためです。

いったんプロジェクトを立ち上げた後、WSL2で使っているグループ名とユーザー名を控えてから `chown` でプロジェクトのディレクトリに対して一括で設定変更すると解決します。

```bash
$ id
uid=1000(yoshida-m) gid=1000(yoshida-m) groups=1000(yoshida-m),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),1001(docker)
```

```bash
sudo chown -R yoshida-m:yoshida-m src
```

このほかに何かいい方法がないか探っています。

## 参考

Windows10 WSL2にLinux居城を爆誕させる - Qiita
https://qiita.com/v2okimochi/items/f53edcf79a4b71f519b1#%E9%96%8B%E7%99%BA%E7%94%A8%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%81%AFwsl%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E5%86%85%E3%81%AB%E3%81%97%E3%81%A6%E3%81%8A%E3%81%8F

wsl2が異常に遅くてホットリロードも効かないとき - Tamaosa
https://www.tamaosa.com/2020/wsl2/