賛否ありますが、Railsにベッタリの開発をしていると、なるべくRailsに寄せて考えたくなることがあります。
そんな
- Webpacker 使ってるプロジェクト
- 環境によって変わる値を環境変数ではなく Credentials にまとめたい。分散させたくない。
- フロントエンドでも使いたい。たとえば Firebase の Project ID や API Key のようなDev,Stg,Prod環境ごとに違って、かつフロントエンドコードに含めて問題ないもの。
そんなときの話です。
- webpack 4.41.5
- webpack-dev-server 3.10.2
- webpacker 4.2.2
- Rails 6.0.1
Webpacker::Compiler.env は bin/webpack-dev-server を使うとうまくいかない
こんな Credentials があったとき
# bundle exec rails credentials:edit --environment=development
firebase:
project_id: FIREBASE_PROJECT_ID
api_key: FIREBASE_API_KEY
auth_domain: FIREBASE_PROJECT_ID.firebaseapp.com
Rails から Webpacker に環境変数を渡すには Webpacker::Compiler.env
が使えます。
Pass custom environment variables to the compiler #691
Webpacker::Compiler.env["FIREBASE_API_KEY"] = Rails.application.credentials.dig(:firebase, :api_key)
Webpacker::Compiler.env["FIREBASE_AUTH_DOMAIN"] = Rails.application.credentials.dig(:firebase, :auth_domain)
Webpacker::Compiler.env["FIREBASE_PROJECT_ID"] = Rails.application.credentials.dig(:firebase, :project_id)
import * as firebase from "firebase/app";
import "firebase/auth";
// "FIREBASE_PROJECT_ID: FIREBASE_PROJECT_ID"
console.log(`FIREBASE_PROJECT_ID: ${process.env.FIREBASE_PROJECT_ID}`);
const config = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
projectId: process.env.FIREBASE_PROJECT_ID
};
firebase.initializeApp(config);
export default firebase;
これは、Rails Server にアクセスした際にビルドする場合や、 assets:precompile
でビルドする場合には動くのですが、 bin/webpack-dev-server
では動作しません。
困ったことに bin/webpack-dev-server
を使ったHMRの快適さに慣れると、いちいちF5なんてやってられません。
$ bin/webpack-dev-server
import * as firebase from "firebase/app";
import "firebase/auth";
// "FIREBASE_PROJECT_ID: undefined"
console.log(`FIREBASE_PROJECT_ID: ${process.env.FIREBASE_PROJECT_ID}`);
const config = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
projectId: process.env.FIREBASE_PROJECT_ID
};
firebase.initializeApp(config);
export default firebase;
Webpacker::Compiler.env はどう使われるか?
Webpacker::Compiler.compile
rails assets:precompile
を実行したり、ビュー中で javascript_pack_tag
ヘルパーを実行したりすると、 Webpacker::Compiler.compile
が呼ばれます。( javascript_pack_tag
は webpack-dev-server
を実行していないときのみ。このへんのコードによる )
Webpacker::Compiler.compile
は Webpacker::Compiler.env
の値を環境変数に設定した上で bin/webpack
を実行します。
bin/webpack-dev-server
bin/webpack-dev-server
を実行したときには、 Webpacker::Compiler.compile
は呼ばれません。
config/webpacker.yml
から設定を読み出すなどした後、直接 node_modules/.bin/webpack-dev-server
を実行します。このとき環境変数として Webpacker::Compiler.env
を渡します。
Webpacker と環境変数
通常、Webpack では、JavaScriptモジュール中の process.env.HOGE
の値は未定義値となります。
Webpacker では標準で Webpack の EnvironmentPlugin を使って、これができるようになっています。
Webpacker しか知らないと参照できて当たり前のように感じてしまいますが、これは Webpacker が気を回してくれてたからなんですね。
const { environment } = require("@rails/webpacker"); // この environment
// こうすると EnvironmentPlugin があることがわかる
console.log(environment.plugins);
module.exports = environment;
この2つの仕組みにより Webpacker::Compiler.env
に設定した値を JavaScript モジュール内で参照できるようになっています。
bin/webpack-dev-server ではなぜ Webpacker::Compiler.env が機能しないか
上記
bin/webpack-dev-server
を実行したときには、Webpacker::Compiler.compile
は呼ばれません。
config/webpacker.yml
から設定を読み出すなどした後、直接node_modules/.bin/webpack-dev-server
を実行します。このとき環境変数としてWebpacker::Compiler.env
を渡します。
の通り、 bin/webpack-dev-server
でも Webpacker::Compiler.env
は使っています。
しかしながら、 assets:precompile
や javascript_pack_tag
と違い、機能しません。
これは、 bin/webpack-dev-server
では、Railsを起動しないため config/initializers/webpacker.rb
に書いたコードは実行されないためです。
bin/webpack-dev-server でも Webpacker::Compiler.env を使う
A. config/initializers/webpacker.rb
を実行する
bin/webpack-dev-server
に次の2行を追加すると、Railsの初期化が走り、 initializers で Webpacker::Compiler.env
を設定できます。
しかし「Rails.application.credentials
にアクセスしたい」だけなのに、これはやり過ぎです。
require_relative '../config/application'
Rails.application.initialize!
B. config/initializers/webpacker.rb
と bin/webpack-dev-server
でコードを共有する
Rails.application.credentials
はRailsの初期化処理なしでもアクセスできます。
たとえば次のようにして共有できます。
Webpacker::Compiler.env["FIREBASE_API_KEY"] = Rails.application.credentials.dig(:firebase, :api_key)
Webpacker::Compiler.env["FIREBASE_AUTH_DOMAIN"] = Rails.application.credentials.dig(:firebase, :auth_domain)
Webpacker::Compiler.env["FIREBASE_PROJECT_ID"] = Rails.application.credentials.dig(:firebase, :project_id)
require_relative "../webpacker_env"
# 追加
require_relative '../config/application'
require_relative "../config/webpacker_env"
あとがき
がんばって Rails / Webpacker でなんとかするために Webpacker のコード読むぐらいなら Webpacker 捨てて Webpackの流儀でやれ、という気持ちになりましたが、一応動かすことはできました。
設定情報の二重管理をせず、手間をかけず、もっと自然に共有する方法はないものだろうか?