Nuxt.js + Typescript + Docker + CircleCI で環境変数を設定して環境構築する

# 目的
- Nuxt.js + Typescript + Docker + CircleCI を使って、環境変数を設定してNuxtプロジェクトをビルドします。

# 環境
- Node: v10.15.2
  - [nodenv](https://github.com/nodenv/nodenv)を利用し、プロジェクトごとにバージョンを指定することがおすすめです。

- Docker: version 18.09.2


# 利用するパッケージ

#### [cross-env](https://www.npmjs.com/package/cross-env)

`cross-env`はyarn or npm スクリプト実行時に環境変数を定義できます。
今回は`cross-env`と`NODE_SETTINGS`という環境変数を用いて環境の切り替えを行います。

# 記事の主となるファイル

```yaml

.
├── Dockerfile # NODE_SETTINGS引数を受け取り、yarnスクリプトを実行します
├── README.md
├── assets
├── .circleci
│   ├── config.yml # Dockerイメージ生成時に --build-args でNODE_SETTINGS引数を引き渡します
├── components
├── config # 環境ごとの変数を定義したファイルを格納するディレクトリです
│   ├── README.md
│   ├── development.ts # 開発環境用の環境変数を定義するファイルです
│   └── production.ts # 商用環境用の環境変数を定義するファイルです
├── css
├── layouts
├── middleware
├── nuxt.config.ts # NODE_SETTINGSに従ってconfigファイルを読み込みます
├── package.json # 環境ごとのscriptを追加します
├── pages
├── plugins
│   └── axios.ts # 環境変数の使用例を記述します
├── server
├── static
├── store
├── tests
├── ts-shim.d.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
```

----

# 各種インストール
#### cross-envをinstall

今回はyarnを利用してパッケージの管理をしていきます。

```shell

$ yarn add cross-env
```

(npmを利用する際はこちらのコマンドをご利用ください)

```shell

$ npm install cross-env
```

# 使用方法

## 1. 環境ごとの変数を定義したファイルを作成する
今回は以下の2つの環境を作成できるようにします。
作成する環境に従って、 `config/{環境名}.ts`ファイルを作成します。

| 環境名 | 備考 |
| --- | --- |
| developmemt| 検証環境 |
| production | 商用環境 |

- config/development.ts

```typescript

module.exports = {
  API_HOST: 'https://development-api-url/'
}
```

- config/production.ts

```typescript

module.exports = {
  API_HOST: 'https://production-api-url/'
}
```

環境ごとにどちらのファイルを読み込むのかは後述で説明します。



## 2. Dockerfileの編集
Dockerfileの編集を行います。
イメージ生成する際、NODE_SETTINGS変数を受け取り、yarnスクリプトを実行します。

```dockerfile


FROM node:10.15.2
# ビルド時に設定する環境変数を受け取っています
ARG ENV_SETTINGS="development"

COPY . /app
WORKDIR /app

RUN apt-get update
RUN yarn install && \
    # 環境ごとのyarnスクリプトを実行
    yarn run build:${ENV_SETTINGS}

ENV HOST 0.0.0.0
EXPOSE 3000

# 環境ごとのyarnスクリプトを実行(※注意1)
CMD yarn start:${ENV_SETTINGS}

```

こうすることにより、

```shell

docker build --build-arg ENV_SETTINGS=${development or production} -t {tag名} .
```

を実行することで、環境ごとのDockerイメージを生成することができます。

(注意1)
exec形式だと、変数の展開を行わないので、`${ENV_SETTINGS}`を正しく読み取ることができません。
> シェル 形式と異なり、 exec 形式はコマンド・シェルを呼び出しません。つまり、通常のシェルによる処理が行われません。例えば CMD [ "echo", "$HOME" ] は $HOME の変数展開を行いません。シェルによる処理を行いたい場合は、 シェル 形式を使うか、あるいはシェルを直接使います。例： CMD [ "sh", "-c", "echo", "$HOME" ] 。


([公式リファレンス](http://docs.docker.jp/v1.11/engine/reference/builder.html#cmd))

## 3. package.jsonの修正(script追記)

環境ごとのyarnスクリプトを実行し、Nuxtプロジェクトに`ENV_SETTINGS`環境変数を渡します。

```json
{
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start",
    "build:development": "cross-env ENV_SETTINGS=\"development\" nuxt build",
    "start:development": "cross-env ENV_SETTINGS=\"development\" nuxt start",
    "build:production": "cross-env ENV_SETTINGS=\"production\" nuxt build",
    "start:production": "cross-env ENV_SETTINGS=\"production\" nuxt start",
  }
}
```

## 4. Nuxt.jsの設定ファイル(nuxt.config.ts)の設定ファイルを編集

yarnスクリプトから渡された`ENV_SETTINGS`環境変数によって、読み込むファイルを決めます。

```typescript

// 渡されたENV_SETTINGSを格納しています。ENV_SETTINGSが設定されていなければdevelopmentを格納します。
const environment = process.env.ENV_SETTINGS || 'development';
// 環境ごとの設定ファイルを読み込んでいます。
const envSet = require(`./config/${process.env.ENV_SETTINGS}.ts`)

const config = {
  plugins: [],
  modules: [],

  // 以下の設定を追記してください。
  /*
  ** Load configuration file according to ENV_SETTINGS variable
  */
  env: envSet
  // ここまで

}

export default config
```

## 5. 環境変数を使用する
Nuxtプロジェクトの中では、`process.env.{環境変数名}` で利用することができます。

- (例)axios.ts

```typescript

import axios from 'axios'

export default axios.create({
  baseURL: process.env.API_HOST
})
```

## 6. (補足)circleCIの設定を編集
circleCIを用いて自動デプロイを行う場合、以下のような処理を追加します。

```yaml
version: 2
jobs:
  build:
    docker:
      - image: docker:18.09.3
    steps:
      - run:
        name: ...

      - run:
          # Dockerイメージ作成時にENV_SETTINGSを引き渡しています。
　　　     # 今回は、gitのブランチがmasterの場合はproduction, master以外の場合はdevelopmentを格納しています。
          name: Build
          command: |
            env_settings='development'
            if [[ "${CIRCLE_BRANCH}" = 'master' ]]; then env_settings='production'; fi
            docker build --build-arg ENV_SETTINGS=${env_settings} -t {tag名} .
      - run:
          name: Push
          command: docker push {tag名}

  deploy:
   ...

workflows:
   ...

```


## 参考にさせていただいた記事
https://qiita.com/TakahiRoyte/items/c152ad8baa191ed1f8ae
https://qiita.com/noonworks/items/179c71deaf3280fa5a1e