Vue + Dotenv で環境別の開発・運用を考える

# はじめに

Vue.js で SPA のフロントを構築し始めると、開発・検証・本番と環境別に違う情報を管理することになるかと思います。

そこで環境別の情報を `.env` ファイルに記述し、CI/CD に乗せて運用していく中で、ソース管理やファイル構成に困ったことはないでしょうか？

そんな時の一例を紹介しますので、何かの一助になれば幸いです！

※ 一個人の見解ですので必ずしも正解や正攻法ではありません。より良い方法があればお気軽にフィードバック頂ければと思います。



# 課題

`.env` ファイルに環境別の情報（サーバー情報や接続情報など）を記述して、Vue.js のフロントエンドを構築しました。

しかし、運用していくと以下のような課題が発生してきました。

- Git のブランチを環境別に作っているため、環境設定ファイルを管理しにくい
  - `develop` ブランチ内に、 `staging` 環境の環境設定ファイルがある…等
- 環境別ファイルをソースコードと別リポジトリで管理すると、今度は開発がやりにくい
    - ソースコードリポジトリのブランチを切り替えたら、環境設定リポジトリも切り替える必要がある…等

# 解決方法

本記事では下記のステップで各課題を解決していきます。

1. 環境別の変数をファイルごとに管理する
  1.1. [環境別の構成](#環境別の構成)
1. 環境別ファイルをGitで管理する
  2.1. [環境別の構成（Git管理向け）](#環境別の構成git管理向け)
  2.2. [運用例](#運用例)
  2.3. [開発方法](#開発方法)

# 構成・前提

Vue CLI で SPA として構築します。


| 項目    | バージョン |
| ------- | ---------- |
| Vue CLI | 4.5.9      |
| Vue.js  | 2.6.11     |
| npm     | 8.1.0      |

なお、ローカル開発環境のOSは、Windows10を想定しています。

# 環境別の構成

環境別ファイルを構成する上で、Vue CLI の「[モードと環境変数](https://cli.vuejs.org/guide/mode-and-env.html#modes)」を参考にします。

Qiita記事だと下記が参考になります。

https://qiita.com/asapon_rb/items/ed7f0f3d81a367e5c109

## 環境

本記事では下記の環境を想定します。


| 環境名      | 内容     |
| ----------- | -------- |
| development | 開発環境 |
| staging     | 検証環境 |
| production  | 本番環境 |

## .env ファイル

環境に従って、モードごとの `.env` ファイルを作成します。

作成した `.env` ファイルは下記のファイル構成の通り配置します。

### ファイル構成

```
    app
    ├─ node_modules
    ├─ public
    │   ├─ favicon.ico
    │   └─ index.html
    │
    ├─ src
    │   ├─ assets
    │   │   └─ logo.png
    │   │ 
    │   ├─ components
    │   │   └─ HelloWorld.vue
    │   │
    │   ├─ App.vue
    │   └─ main.js
    │ 
    ├─ .env.development        # 追加
    ├─ .env.production         # 追加
    ├─ .env.staging            # 追加
    ├─ .gitignore
    ├─ babel.config.js
    └─ package.json
```

ルートディレクトリ（`app`）下に `.env.[mode]` ファイルが環境分並びます

### .env ファイル内容

例として、`.env.staging` の内容を記載します。


```:.env.staging
NODE_ENV='production'
VUE_APP_MODE='staging'
```

:::note info
**ポイント**
環境変数 `VUE_APP_MODE` がモードを表す変数になります。
`.env.[mode]` の `[mode]` 部分を変数の値として設定します。
:::

:::note warn
**注意点**
紛らわしいですが、`NODE_ENV` は Node.js の環境変数となるため、値（`'production'`）は関係ありません。
:::

## package.json

モードを適用してビルドできるように `package.json` を編集します。


```json:package.json
...
  "scripts": {
    "serve": "vue-cli-service serve --mode staging",
    "build-development": "vue-cli-service build --mode development",
    "build-staging": "vue-cli-service build --mode staging",
    "build-production": "vue-cli-service build --mode production",
    "lint": "vue-cli-service lint"
  },
...
```


## 環境変数の確認

### main.js

確認のため、環境変数としてビルドされた `process.env.VUE_APP_MODE` の値を出力します。


```javascript:main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

console.log(process.env)  // 追加

new Vue({
  render: h => h(App),
}).$mount('#app')
```


### ローカルサーバー起動

モードを `staging` に指定してローカルサーバーを起動します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1215832/894ed546-c43d-f746-7eee-790322f40363.png)



`NODE_ENV`、`VUE_APP_MODE`  が出力されました（他のキーはデフォルトで設定されます）。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1215832/fd900988-1064-7f5d-3983-922283247329.png)



この時点で、**環境別ファイルの設定は完了している**ので、これでも問題なく開発していけます。

しかし、上述している運用的な課題を解決するべく、もう少し手を加えます。



# 環境別の構成（Git管理向け）

今回は「環境別ファイルを独立した別のリポジトリで、かつ環境ごとのブランチで管理したい。」という要件がありました。
しかし、標準機能として扱える `.env.[mode]` ファイルは、ルートディレクトリに配置する必要があります。
そこで、Node.js のライブラリである [dotenv](https://github.com/motdotla/dotenv) と、[dotenv-expand](https://github.com/motdotla/dotenv-expand) を用いて、ファイル構成や環境別ファイルを変更していきます。

## dotenv + dotenv-expand

プロジェクトに dotenv と dotenv-expand をインストールします。



```shell
npm install dotenv --save-dev
npm install dotenv-expand --save-dev
```



## envフォルダ

環境別ファイルを格納するフォルダおよび環境別ファイルを新規作成します。

### ファイル構成

    app
    ├─ env                     # 追加
    │   ├─ development         # 追加
    │   │   └─ .env            # 追加
    │   │   
    │   ├─ production          # 追加
    │   │   └─ .env            # 追加
    │   │   
    │   └─ staging             # 追加
    │       └─ .env            # 追加
    │       
    ├─ public
    (以下省略)


## vue.config.js

[vue.config.js](https://cli.vuejs.org/config/#vue-config-js) をルートディレクトリ下に追加します。

`vue-cli-service` 実行時に自動で読み込まれるファイルです。



```javascript:vue.config.js
var dotenv = require('dotenv').config({ path: `./env/${process.env.VUE_APP_MODE}/.env` })
if (dotenv.error) {
  throw dotenv.error
}
require('dotenv-expand')(dotenv)
```



:::note info
**ポイント**
- 1行目: 環境変数 `VUE_APP_MODE` を用いて、上記で作成した `env` フォルダ下の `.env` ファイルをパスに指定
- 5行目: `dotenv` を `dotenv-expand` で拡張
:::


-----

これで、環境別フォルダ下（`app/env/`）に、環境別ファイル（`.env`）を配置することができました。

## env/{環境名}/.env

ファイルの中身の書き方としては、`dotenv` や、`dotenv-expand` に習います。



```:env/staging/.env
# Can use $VUE_APP_MODE, $NODE_ENV

# .env local variables
CLIENT_ID='abcde'

# Vue environment variables
VUE_APP_API_URL='https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/${CLIENT_ID}/${VUE_APP_MODE}'
```



※ 環境変数を設定した場合は、サーバーを再起動してください。


:::note info
**ポイント**
- 4行目: `CLIENT_ID` はこの `.env` ファイル内でのみ使用可能です。
  埋め込み変数として使用したいけれど、Vue アプリケーション上に必要ないという場合に有用です
- 7行目: 例として `VUE_APP_API_URL` というキーで、環境別のAPIのURLを設定してみました。
  ここでは、`.env.staging` 内で定義している環境変数も使用できるので、`${VUE_APP_MODE}` を埋め込んでいます
:::


## 環境変数の確認

ローカルサーバーを再起動して環境変数を確認します。



`VUE_APP_API_URL` が出力されました。埋め込み文字列も変換されています。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1215832/c033741a-6ce7-3435-5252-8d602335d1ec.png)


:::note warn
**注意点**
- 同じ js ファイルで混同しやすいですが、`src` フォルダ以下（`main.js` など）が Vue.js のモジュールです
  `vue.config.js` は Node.js のモジュールになります
- Vue CLI のドキュメント通り、Vue.js の環境変数は、プレフィックスとして `VUE_APP_` を付加してください
- 埋め込み文字列が不要な方は、`dotenv-expand` のセットアップが不要となります
:::




# 運用例

それでは「[環境別の構成（Git管理向け）](#環境別の構成git管理向け)」にて基盤を整えたところで、Git の運用例や CI/CD の設定例を見ていきます。



## ソースコードリポジトリ

Vue アプリケーションのソースコードを管理するリポジトリは、下記の通り環境別にブランチを切ります。

なお、ブランチ間（環境間）ではソースコードや、ファイル構成の差異は発生しません。



```:ソースコードリポジトリ構成
SourceCodeRepository　　　　　　　　　　　# ソースコード用のリポジトリ
├─ development 　　　　　　　　　　　　　 # 開発環境ブランチ
│   └─ (省略: 下記ファイル構成の通り)
│   
├─ production  　　　　　　　　　　　　　 # 本番環境ブランチ
│   └─ (省略: 下記ファイル構成の通り)
│   
└─ staging     　　　　　　　　　　　　　 # 検証環境ブランチ
    └─ (省略: 下記ファイル構成の通り)
```



## env フォルダ下の除外

ソースコードリポジトリから、`env` フォルダ下の `.env` ファイルをGit管理対象外とします。



### .gitignore

`app/.gitignore` に追記します。



```:.gitignore
...(省略)

# 追加
env/*.env

...(省略)
```



### ファイル構成

最終的なソースコードリポジトリのファイル構成は以下の通りです。



```:ソースコードリポジトリ・ファイル構成
    app
    ├─ env                     
    │   └─ .gitkeep            # フォルダ構成を保つために追加
    ├─ node_modules
    ├─ public
    │   ├─ favicon.ico
    │   └─ index.html
    │
    ├─ src
    │   ├─ assets
    │   │   └─ logo.png
    │   │ 
    │   ├─ components
    │   │   └─ HelloWorld.vue
    │   │
    │   ├─ App.vue
    │   └─ main.js
    │ 
    ├─ .env.development        # 全環境残したままです
    ├─ .env.production         # 全環境残したままです
    ├─ .env.staging            # 全環境残したままです
    ├─ .gitignore
    ├─ babel.config.js
    ├─ package.json
    └─ vue.config.js
```



## 環境ファイルリポジトリ

`env` フォルダ下の環境別ファイルは、ソースコードとは別のGitリポジトリで管理します。

構成は以下の通りです。



```:環境ファイルリポジトリ構成
    EnvironmentRepository   # 環境ファイル用のリポジトリ
    ├─ development          # developmentブランチ
    │   └─ .env             # 環境別ファイル
    │   
    ├─ production           # productionブランチ
    │   └─ .env             # 環境別ファイル
    │   
    └─ staging              # stagingブランチ
        └─ .env             # 環境別ファイル
```



## Git運用例

ここまでの設定によって、環境別の情報を管理する際の気持ち悪さが解消できました。
ただ、リポジトリを2つ運用することになるので、その点はご注意ください。



### ソースコードリポジトリ

ソースコードリポジトリは、プルリクエストを基本としたよくある運用です。
プルリク→マージ→リリースとステージングしていきます。



### 環境ファイルリポジトリ

環境間の `.env` ファイルは必ず差分があるので、マニュアルで比較しながら各ブランチにマージします。



1. `development` ブランチの `.env` ファイル修正
2. `development` ブランチと比較しながら、`staging` ブランチの `.env` ファイル修正
3. `staging` ブランチと比較しながら、`production` ブランチの `.env` ファイル修正



## CI/CD設定例

各環境のパイプラインでは、下記のような手順でビルドします。
※ `development` 環境を例示します



1. ソースコードリポジトリの development ブランチを `clone`
2. 1.の `app/env/` 下にディレクトリ指定で、環境ファイルリポジトリを `clone`
3. `git clone -b development  https://{host}/app.git development`
4. `vue-cli-service build --mode development` でソースコードをビルド




# 開発方法

最後に、開発方法についても記載します。



`env` フォルダ下に環境ファイルリポジトリを `clone` すれば良いようにも感じますが、Gitリポジトリの中に別のGitリポジトリがあるのは望ましくありません。



よって、ここではシンボリックリンクを使用します。



## 環境ファイルを集約

ローカルで開発しているソースコードリポジトリとは別のディレクトリに、各ブランチを `clone` します。

ここでは `C:\Dev\environments` を作成します。



```msshell:コマンドプロンプト
cd c:\Dev\environments
git clone -b development https://{host}/app.git development
git clone -b staging https://{host}/app.git staging
git clone -b production https://{host}/app.git production
```



### クローン結果

`environments` フォルダ内。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1215832/adeaaa25-bddd-9ef2-040c-d356149757f8.png)



各ブランチフォルダ内は、`.env` ファイルのみ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1215832/08003f3c-04ac-e280-5bb5-5f902b2aecee.png)


## シンボリックリンク作成

ローカルで開発しているソースコードリポジトリの `env` フォルダ内でコマンドプロンプトを開き、以下のコマンドを実行します。

※ 例では `development` 環境でローカルサーバーを起動しています。



```msshell:コマンドプロンプト
mklink /j development c:\Dev\environments\development
mklink /j staging c:\Dev\environments\staging
mklink /j production c:\Dev\environments\production
```



シンボリックリンク（ジャンクション）を作成することで、`env` フォルダ下を除外する前の構成に戻りました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1215832/1e90194b-8b83-6335-aa4d-8f6e91190b81.png)



## 開発方法まとめ

`env` フォルダ下はGit管理対象外なので、もし他のブランチにチェックアウトしても、`env` フォルダ下は消えたりしません。

ソースコードのブランチを変えるたびに、環境別ファイルをコピーしてきたりする手間を省くことができます。