node_modulesをPCにインストールせずにVue + Vuetifyの開発環境をDockerで作る

# 何をしたいか
複数メンバーでVuetifyを使って軽く開発をすることになったのだが、各メンバーのスキルにバラツキがあるため（殆どプログラミング経験ないメンバー有）、出来るだけ事前準備無しで環境を作ろうと思ったのがきっかけである。

全員VScodeを使うが、ソースコードを置く場所がWindowsだったりMacだったり、リモートのCentOSマシンだったりとバラバラ。

それぞれの環境でnpm installしてnode_modulesのフォルダが出来て、となると結構面倒なので、git cloneしてdocker-composeすれば全て用意されるようにしたかった

今回の記事では以下の事を実現する

- コンテナ上でnpm installをしてnpm run buildも行うが、ソースコードはホスト上に置いておきたい
- node_modulesはコンテナ、ソースコードはホスト上、出来上がったdistもホスト上としたい
- Dockerfileにコマンドを書くのではなく、package.jsonからまとめてインストールしたい
- いちいちnpm run buildを実行したくないので`vue-cli-service build --watch`として自動ビルドしたい
- FlaskのAPIサーバを使うのでNGINXのリバースプロキシも必要なため、distをNGINXに見せる

# Git
https://github.com/legacyworld/vuetify_test

#フォルダ構成

```tree
.
|-- app
|   |-- Dockerfile
|   `-- src
|-- docker-compose.yml
|-- nginx_web
|   |-- Dockerfile
|   `-- nginx_conf
`-- vuetify
    |-- Dockerfile
    |-- README.md
    |-- babel.config.js
    |-- dist
    |-- node_modules
    |-- package-lock.json
    |-- package.json
    |-- public
    |-- src
    `-- vue.config.js
```
# Docker Compose

NGINX,Flask,Vuetifyビルド用ということで3つコンテナが立つことになる

```docker-compose.yml
version: '3'
services:
  nginx_web:
    build:
      context: ./nginx_web
      dockerfile: Dockerfile
    container_name: nginx_web
    ports:
    - 80:80
    volumes:
    - ./vuetify:/usr/share/nginx/html

  vuetify:
    build:
      context: ./vuetify
      dockerfile: Dockerfile
    container_name: vuetify
    ports:
      - 1234:8080
    volumes:
      - ./vuetify:/app
      - /app/node_modules
    stdin_open: true
    tty: true

  app:
    build:
      context: ./app
      dockerfile: Dockerfile
    container_name: app
    ports:
      - 3031:3031
    volumes:
    - ./app/src:/src
```

nginx_webとapp(flask)コンテナの設定についてはこちらの記事を参照
[jwilder nginx-proxy + letsencrypt-nginx-proxy-companion + Flask + uwsgiでHTTPS対応のWebサービスを作り、更にQualysでA+を取る](https://qiita.com/legacyworld/items/32438b8a412718e2d48e)
nginx_webの設定はVuetifyのbuildに対応するため少し変更してあるので、後ほど説明する

vuetifyの部分のポイントは`volumes`の`/app/node_modules`
コンテナ上にnode_modulesを展開するため、コンテナ上の/app/node_modulesはvolumeとして別途確保している。

# Dockerfile

使うモジュールが増えた時に`npm install <module>`を書き足すのが嫌なので、package.jsonから`npm install`で片づけたい。

```Dockerfile
FROM node:alpine

WORKDIR /app

COPY ./package*.json ./

RUN apk update
RUN npm install -g npm
RUN npm install

CMD ["./node_modules/.bin/vue-cli-service", "build", "--watch"]
```
package.jsonには今回利用するvue,vuetify,axiosが記載されている。
PC上で一回npm installして自動で作成されたpackage.jsonをコピーして利用している

`CMD`にある`--watch`は変更ファイルを見張って自動でビルドしてくれる便利機能
ここで書かずにpackage.jsonを以下のように書いて、`CMD ["npm","run","build"]`としても良い

```package.json
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build --watch",
    "lint": "vue-cli-service lint"
  },
```

# NGINX設定
index.htmlが`./vuetify/dist`に出力されるのでNGINXのデフォルトのフォワード先をここにする必要がある
それが以下の記述

```nginx.tpl
  server{
    listen 80;
    server_name nginx;
    location / {
      root   /usr/share/nginx/html/dist;
      index  index.html index.htm;
    }
```

docker-compose.ymlで

```
    volumes:
    - ./vuetify:/usr/share/nginx/html
 ```

としているので、`http://localhost`でアクセスが来たら`./vuetify/dist/index.html`が読み込まれる

# Build
`docker-compose up --build -d`とすればOK
一回目はvuetifyのビルドに数十秒かかるのでコンテナが上がってもしばらく待とう。さもなくば404が表示される
うまくいけば以下のように表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/7d20f4b2-a99c-9160-0d46-c1557bfac4a1.png)

#Flaskとの連携
`Read Data from Flask`という部分がVuetifyのデフォルトページに追加した部分である
ソースコードはこんな感じ

```server.py
from flask import Flask, jsonify, request, make_response,send_from_directory
import requests
import json

app = Flask('test')
@app.route('/api/v1/get/',methods=['GET'])
def get():
    return "Read Data from Flask",200
```

ただ文字を返しているだけのシンプルなAPIとしている

# VueからのAPI呼び出し
Vueから先ほどの`/api/v1/get`を呼び出しているのがこの部分
vuetifyデフォルトのHelloWorld.vueに以下の部分を追加している

```HelloWorld.vue
<template>
...省略
      <v-col cols="12" class="mb-4">
        <h1 class="display-2 font-weight-bold mb-3">
          {{flask_message}}
        </h1>
      </v-col>
...省略
<script>
import axios from "axios"

  export default {
    name: 'HelloWorld',

    created: function() {
      axios.get('/api/v1/get')
      .then(response => {
        this.flask_message = response.data
        })
    },
    data: () => ({
      flask_message: '',
...省略
```

# 結果
npmをインストールしていないWindows/Mac,Ubuntuなどで問題なく作動した。
vueファイルを変更してもビルドは非常に速い（1秒以下）
これで快適に開発が出来るようになった。