背景
別にAngularやSPAに限った話ではないが, アプリケーションの開発をしていたら開発/本番で幾つかのパラメータを切り替えたい、といったようなビルドに関連する要求は珍しくない.
今回は, AngularJSとgulpを使っている開発において, このようなビルド要求を解決するために, 現時点で僕がベストプラクティスと考えている方法をメモも兼ねて残しておく.
ユースケース
ビルドによるパラメータの切替の例として, 真っ先に思いつくのはAPIの接続先変更である.
下記のような要件だったとしよう.
- 開発時:
localhost:5080
を接続先にしたい - 本番時: URLのPrefixに
/apiContextRoot
を付与したい
こんなときは, 次のようにアプリケーションの設定を切り出す.
- アプリ本体のコード(開発/本番で共用)
angular.module('sampleApp', ['sampleApp.Conf']);
angular.module('sampleApp').controller('MainCtrl', function ($http, APP_CONF) {
var main = this;
$http.get({
url: APP_CONF.urlBase + 'someResourcePath'
}).then(function (data) {
//...
});
});
- 開発環境用の設定コード
angular.module('sampleApp.Conf', []).constant('APP_CONF', {
urlBase: 'http://localhost:5080'
});
- 本番向けの設定コード
angular.module('sampleApp.Conf', []).constant('APP_CONF', {
urlBase: '/apiContextRoot'
});
index.htmlから読み込むファイルを切り替えることで, 環境の差に対応できるようになったわけだ.
- 開発時:
config_dev.js
- 本番時:
config_prod.js
実例
ここからは, 上述のユースケースを例に, 実際に僕が普段使っている開発環境を一部簡素化して説明してみる.
Directory構成
ビルドの話なので、ディレクトリ構成の話題は避けて通れない.
ここでは, 以下のような構成を前提としている(Yeomanのgenerator-gulp-angularで生成することが多いため).
(PROJECT ROOT)
├ ng-config
│├ config_dev.json
│└ config_prod.json
├ src
│├ app
││├ main.controller.js
││├ (その他js, html色々)
││└ index.js
│└ index.html
├ bower.json
├ gulpfile.js
├ package.json
└ (other files)
設定ファイルの設置
先述のconfig_dev.jsやconfig_prod.jsについて, 以前は自分で作成・記述していたのだが, AngularJSのモジュール定義(angular.module(...).constant
)が入ってしまっているために直接Nodeやgulpから読みづらく, 不都合を感じることがあった.
このため, 最近は gulp-ng-configで, config_***.json
→ config_***.js
を生成するようにしている.
作成するJSONは下記のようになる. constantの中身をJSONとして取り出すだけだ.
{
"APP_CONF": {
"urlBase": "http://localhost:5080"
}
}
{
"APP_CONF": {
"urlBase": "/apiContextRoot"
}
}
gulp-plugin
ビルドに必要なgulpのプラグインは次の通り:
npm install gulp gulp-load-plugins gulp-inject gulp-ng-config --save-dev
gulp-ng-configとgulp-injectによる設定の注入
<html>
:
<!-- build:js({app,.tmp}) scripts/app.js -->
<!-- inject:js -->
<!-- endinject -->
<!-- endbuild -->
:
</html>
今回、解説するのは<!-- inject:js -->
の部分であるが, 大概のケースでは gulp-userefやgulp-useminをつかうための<!-- build:js -->
のブロック内に設置されることだろう.
gulpfile
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
/** 1. Profileの特定. 環境変数からも取得可能にしておく. **/
var profile = process.env.PROFILE || 'dev';
/** 2. .json→anuglarの設定モジュール生成タスク **/
gulp.task('ngconfig', function () {
return gulp.src(['ng-config/config_' + profile + '.json'])
.pipe($.ngConfig('sampleApp.Conf'))
.pipe(gulp.dest('.tmp'))
;
});
/** 3. 生成した.jsとアプリケーションのjsソースをhtmlへ注入 **/
gulp.task('injector:js', ['ngconfig'], function () {
return gulp.src(['src/index.html'])
.pipe($.inject(gulp.src([
'.tmp/config_' + profile + '.js',
'src/app/index.js',
'src/**/*.js'
]), {
ignorePath: ['src', '.tmp'],
addRootSlash: false
}))
.pipe(gulp.dest('src/'))
;
});
ポイントは, $.ngConfig
に設定モジュール名('sampleApp.Conf'
)を喰わせるてpipeすることでconfig_***.js
が .tmp
ディレクトリに作成される点と, 作成したconfig_***.js
をgulp-injectで注入している点.
なお、gulp-injectは「gulpfile.jsのディレクトリを起点とした相対パス」を<script src="...">
に埋め込んでいくため, index.htmlとgulpfile.jsが別ディレクトリに配置されているケースでは, 相対パスの基準調整が必須となる.
ここでは, gulp-injectのオプションignorePath
にて, .tmp/
とsrc/
を切り取るようにしている.
これで, 次のようにgulpを叩けば, デフォルトのProfileとしてconfig_dev.jsが注入される.
gulp injector:js
src/index.html
を開いたら, 下記のように設定ファイルが注入されているのを確認できるはず.
<html>
:
<!-- build:js({.tmp,src}) scripts/app.js -->
<!-- inject:js -->
<script src="config_dev.js"></script>
<script src="src/app/index.js"></script>
<script src="src/app/main.controller.js"></script>
<!-- その他のjs色々 -->
<!-- endinject -->
<!-- endbuild -->
:
</html>
また, 下記のように環境変数でProfileを切り替えることで, 本番用のビルドにも対応できる.
export PROFILE=prod
gulp injector:js
レポジトリのファイルに一切手を加えることなく開発/本番の切替が行えるので, Travis CIやJenkins等, CIに組み込むのも容易だ.
まとめ
- gulp-ng-config, gulp-injectで設定ファイルの注入の仕組みが簡単に作れる.
他にも色々な方法があると思うので, よりよいやり方とかあったら教えてほしいなー。
おまけ
gulp-ng-configを知る前に, node.jsでもAngularJSでも読み込める(require/$inject)ように↓のような設定ファイルを作る、というプランを考えたこともあるが, あまりにもアホくさいのでやめた。
'use strict';
(function (factory) {
factory({
urlBase: 'http://localhost:5080'
});
})(typeof angular !== 'undefined' ? function (config) {
return angular.module('sampleApp').constant('APP_CONF', config);
} : function (config) {
module.exports = config;
});