yasuda

dotenvとcross-envで環境変数を設定して開発環境の処理を切り替える

開発用と公開用で読み込むデータを変更したい、Gitで同じ開発環境を共有しながら一部のデータは作業者のPCで設定できるようにしたい。
要件が複雑な案件では、開発環境もより柔軟性のある設定にしておきたいことがあります。

たとえば、https環境が必須の機能を検証するためにbrowser-syncの設定が必要な場合です。各自のPCで証明書の発行が必要なので、Gitで共有することができません。
Google Maps APIやデータベースと連携するAPIを開発用と公開用で変更する場合もあります。切り替えるたびにソースコードを書き換えていると、本来不必要なコミット履歴が増え、開発用のAPIのまま本番環境に公開してしまったなんてことも起こってしまうかもしれません。

今回は柔軟性のある開発環境にするために便利な、環境変数とenv(エンブ)を紹介します。

環境変数とenv(エンブ)とは

環境変数とは、OSやアプリケーション開発環境などの実行環境が持っている、自身の状態を表す変数です。
envとは、環境変数を参照したり編集する仕組みのことです。環境変数を操作するLinuxのenvコマンドから来ています。

GulpやwebpackはNode.jsで動いてます。この実行環境の環境変数はprocess.env経由で参照するのが一般的です。
コマンドを実行して環境変数を設定してもいいのですが、フロントエンドの開発環境ならdotenvcross-envを使うのが一般的です。

dotenvとは

開発環境のルートに環境変数を設定した.envファイルを作成すると、process.env経由で参照してくれるパッケージです。元々はRuby発のOSSですが、今回はNode.js版を使います。

npm install --save-dev dotenv

環境変数は以下のように設定します。
4つのルールを覚えておきましょう(それ以外のルールはこちらを参照)。

  1. #から始める行はコメントになります
  2. KEY1=VALUE1{KEY1: 'VALUE1'}として解釈されます
  3. KEY1={KEY1: ''}として解釈されます
  4. 空行はスキップされます
#これはコメントです
KEY1=VALUE1
#KEY2=VALUE2

KEY3=

gulpfile.jsとwebpack.config.jsからは、require('dotenv').config();とした後にprocess.env経由で結果を受け取れます。
たとえば、KEY1=VALUE1process.env.KEY1から'VALUE1'が受け取れます。

.envは外部に漏れてはいけないデータを含むことが多いため、.gitignoreに追加してコミットされないようにしておきます。

.env

cross-envとは

WindowsでNODE_ENV=productionのように環境変数を設定すると、コマンドプロンプトが停止してしまいます。cross-envはそういったプラットフォームの差異を吸収してくれるパッケージです。Nuxt.jsNext.jsでも公式に紹介されています。

dotenvと同じくnpmからインストールします。

npm install --save-dev cross-env

環境変数の設定は、npmスクリプトでGulpタスクを実行する箇所に追加します。

  "scripts": {
    "start": "cross-env NODE_ENV=development gulp",
    "release": "cross-env NODE_ENV=production gulp build",
  },

上記の例では、npm startprocess.env.NODE_ENVdevelopmentを渡して開発用の処理を、npm run releaseproductionを渡して公開用の処理を実行しています。
NODE_ENVは、実行環境が本番環境なのかを判定する時に使われる環境変数です。productionで本番、developmentで開発、testでテストがよく使われます。

詳しい説明は後述しますが、process.env.NODE_ENVで受け取った値で条件分岐をさせて、専用の設定ファイル(グローバル定数)を読み込むこともできます。

dotenvのユースケース

browser-syncのhttpsオプションに証明書のパスを指定する方法を紹介します。

browser-syncのhttpsオプションに証明書のパスを指定する

コミット対象から外した.envの内容をGitで共有するために、開発環境のルートに.env.exampleを作成して、全体共通の設定を書きます。
今回は、以下のように設定してcp .env.example .envを実行します。

#browser-syncで`https`オプションを使用する場合にコメントを解除します
#HTTPS_KEY=/Users/<ユーザー名>/<IPアドレス>-key.pem
#HTTPS_CERT=/Users/<ユーザー名>/<IPアドレス>.pem

.env.exampleが.envとして複製されるのが確認できます。
このように、.env.exampleを経由して.envの全体設定を共有するのが一般的です。必要に応じて.envの設定を上書きしたり追加します。

HTTPSに必要な証明書はmkcertで作成します(もちろん他の方法でも大丈夫です)。今回はMacのhomebrewを使っています。

homebrewからmkcertをインストールします。

brew install mkcert

Firefoxで確認する場合はnssもインストールする必要があります。

brew install nss

ローカルに認証局を作成します。

mkcert -install

証明書を作成します。
「システム環境設定」の「ネットワーク」を開き、「詳細」ボタンをクリックします。「TCP/IP」のタブの「IPv4アドレス:」をコピーして、以下のように実行します。

mkcert <IPアドレス>

/Users/<ユーザー名>/<IPアドレス>-key.pemと/Users/<ユーザー名>/<IPアドレス>.pemが生成されます。

.envのコメント#を削除して、HTTPS_KEYHTTPS_CERTに証明書へのパスを設定すれば完了です。

#browser-syncで`https`オプションを使用する場合にコメントを解除します
HTTPS_KEY=/Users/<ユーザー名>/<IPアドレス>-key.pem
HTTPS_CERT=/Users/<ユーザー名>/<IPアドレス>.pem

gulpfile.jsから.envに設定した証明書のデータを取得・設定していきます。

dotenvを通して.envのデータを取得します。.envが見つからない場合は無視されるので、条件分岐させるく必要はありません。

require('dotenv').config();

browser-syncのhttpsオプションに、証明書のデータが取得できたら設定、取得できなかったらfalseを渡す(httpを使う)ように設定します。

function serve(done) {
  const httpsOption =
    process.env.HTTPS_KEY !== undefined
      ? { key: process.env.HTTPS_KEY, cert: process.env.HTTPS_CERT }
      : false;
  browserSync({
    https: httpsOption,
  });
  done();
}

serveタスクを実行すると、https環境で表示されるのが確認できます。

cross-envのユースケース

webpack.config.jsのmodeオプションを切り替える処理と、JSファイルにグローバル定数を渡す処理の2つを紹介します。

webpack.config.jsのmodeオプションを切り替える

webpack.config.jsのmodeオプションは、開発と公開で処理を最適化してくれます。たとえば、production時にはJSのMinify(圧縮)やTree Shaking(使用していないコードの除去)をします。
mode:にはdevelopmentproductionnoneのいずれかを指定します。

const environment = process.env.NODE_ENV;

module.exports = {
  mode: environment,
};

上記のように設定すると、npm scriptsから以下のように結果を受け取れるので、実行時のスクリプトで処理を切り替えられます。

  • cross-env NODE_ENV=development => development
  • cross-env NODE_ENV=production => production

JSファイルにグローバル定数を渡す

webpack.config.jsからコンパイル対象のJSファイルにグローバル定数を渡す場合はDefinePluginを使います。

const webpack = require('webpack');
const environment = process.env.NODE_ENV;
const environmentConfig = require(`./config/${environment}.js`); // eslint-disable-line

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env': JSON.stringify(environmentConfig),
    }),
  ],
};

上記では、./config/${environment}.jsの箇所で、読み込むJSファイルを切り替えています。たとえば、npmスクリプトでcross-env NODE_ENV=productionを渡していたら、./config/production.jsをインポートします。参照はprocess.env.KEYのようにします。
DefinePluginでコンパイル対象のJSファイルにも渡しているので、同じように参照できます。

config/development.jsとconfig/production.jsを作成して、以下のようにデータを設定します。

// config/development.js
module.exports = {
  API_BASE_URL: 'dev.example.com',
}
// config/production.js
module.exports = {
  API_BASE_URL: 'prod.example.com',
}

webpack.config.jsやコンパイル対象のJSファイルでconsole.log(process.env.API_BASE_URL);のように実行すると、dev.example.comprod.example.comが出力されるのが確認できます。

まとめ

  • 環境変数は実行している開発環境自身の変数
  • envは環境変数を設定したり参照すること、またはそのパッケージ
  • dotenvはコミット対象外の.envで各自が設定を変更したり、外部に漏らしてはいけないデータを扱う場合に使う
  • cross-envは開発用と公開用でデータや処理を切り替えたい場合に使う

envを使えるようになると、開発環境の柔軟性が大きく上がります。
使ったことがない人は、ぜひ試してみてください。
gulp-ifを使って、開発時の処理を簡素化すると、コンパイル時間の短縮もできるのでオススメです。

新しいウェブ体験を作ろう TAMのPWA開発
お問い合わせはこちら