こんにちは、斉藤です。
最近、マネジメントしながらプログラムを書くことが多いです。
今回WEBアプリ開発のプロジェクトを一つ通して、とある問題とその解決方法を模索しました。
その共有をしたいなーと思いましたので、ブログに書いてみます(ラズベリーパイの記事はまたもお休みです)。
問題
* 顧客に開発中のアプリを見てほしい
* 自分たちはもちろんのこと、顧客にも素早く確認してもらいたい
* アプリが完成に近づけば近づくほど、特定箇所の確認が難しくなる
* 特定の手順を踏まなければならない
* 特定の条件を満たさなければならない
* このためのデバッグ用コードを残しておくと、リリースに影響するため、取り除くことを覚えておかなければならない
* かといって、無ければ無いで開発に不便
こんな問題、思い当たりませんか?
ネイティブアプリなら、プリプロセッサを使っての条件付きコンパイルによるデバッグビルド/リリースビルドで解決出来ますが、JavaScriptを使うWEBアプリではどうでしょうか?
実は、前回紹介したuglify-jsというnpmモジュールには、結合/圧縮だけではなく、この条件付きコンパイルという強力な機能があります。
ということで、上記の問題をこのuglify-jsを使って解決したという内容を共有します。
uglify-js
uglify-jsのインストールは以下の通り。
$ sudo npm install -g uglify-js
さらに、こんなJSを作ります。
$ cat index.js
if (DEBUG) {
console.log("検証用");
} else {
console.log("本番用");
}
これに対して、以下のようにuglify-jsを実行。
- 検証用JS書き出し
$ uglifyjs index.js -c warnings=false --define DEBUG=true --beautify
console.log("検証用");
- 本番用JS書き出し
$ uglifyjs index.js -c warnings=false --define DEBUG=false --beautify
console.log("本番用");
if (DEBUG) {…}部分が条件付きコンパイルとして解釈され、それぞれ2種類のJS(検証用/本番用)を生成できます。
たったこれだけ(それどころか、結合/圧縮までついてくるというおまけ付き)!
基本: 検証用JSのみ、console.logを仕込みたい
まずは基本の使い方を。
開発に使っているconsole.logの結果は、アプリを実際に使うユーザーにはあまり見せたくないですね。これを隠すために、こんな要領で記述します。
$ cat count.js
count = 0;
function countUp() {
count++; // 何かのカウントアップ変数
if (DEBUG) console.log("countは今、こんな値です: " + count);
}
uglify-jsを掛けると、以下のように検証用JSのみconsole.logが働きます。
- 検証用JS書き出し
$ uglifyjs count.js -c warnings=false --define DEBUG=true --beautify
$ function countUp() {
count++, console.log("countは今、こんな値です: " + count);
}
count = 0;
- 本番用JS書き出し
$ uglifyjs count.js -c warnings=false --define DEBUG=false --beautify
function countUp() {
count++;
}
count = 0;
応用: 検証用JSのみ、URLパラメーターからのデバッグオプションを仕込みたい
これが、今回の問題を解決させるために使った方法です。
こんなURLパラメーターを解釈するJS(この例ではjquery.purl.jsを使っています)を仕込んでおき、
$ cat valid.js
function isValidYourStatus() {
if (DEBUG & & $.url().param("valid") !== undefined) {
return true;
}
var isValid = false;
// … 特殊な手順でisValidをtrueにする処理など …
return isValid;
}
同じくuglify-jsを掛けると、以下のように検証用JSのみURLパラメーターを解釈するようになります。
- 検証用JS書き出し
$ uglifyjs valid.js -c warnings=false --define DEBUG=true --beautify
function isValidYourStatus() {
if (void 0 !== $.url().param("valid")) return !0;
var isValid = !1;
return isValid;
}
- 本番用JS書き出し
$ uglifyjs valid.js -c warnings=false --define DEBUG=false --beautify
function isValidYourStatus() {
var isValid = !1;
return isValid;
}
あとはこのJSを読み込んでいるHTMLにアクセスするとき、URLの最後に?validと付けるだけ(http://localhost/?valid )で、関数の返り値を強制的に書き換えるコードも楽々仕込めます。付けなければ、そのまま本番用JSと同じ振る舞いをします。さらに、本番用JSではその機能を使えないように書き出してくれます。
特別な手順を踏まないと、欲しい返り値をくれないような関数には、この方法がとても有効です。
あとは、顧客に"その確認がするんだったら、このURLを踏むと便利だよ!"などと伝えておけば、確認作業がとてもスムーズになりますね。
gruntでもっと楽に
ただ、このコマンドを毎回叩くのも面倒ですよね?
ソースコードを書き換えたときに自動的に実行して欲しくありませんか?
流行りのgruntを使えばそんなことも可能です。以下のGruntfile.jsを参考にしてみてください。
$ cat Gruntfile.js
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
mangle: false,
beautify: false,
compress: {
global_defs: {DEBUG: false},
dead_code: true
}
},
index_all: {files: {'public_html/js/build/index_all.js': 'src/*.js'}},
index_all_dev: {
options: {
beautify: true,
compress: {
global_defs: {DEBUG: true},
dead_code: true}
},
files: {'src/dev/index_all.js': 'src/*.js'}
}
},
watch: {
index: {
files: ['Gruntfile.js', 'src/*.js'],
tasks: ['uglify']
}
}
});
grunt.registerTask('default', ['uglify']);
};
- 参考
http://gruntjs.com/
https://www.npmjs.org/package/grunt-contrib-uglify
https://www.npmjs.org/package/grunt-contrib-watch
まとめ
今回はuglify-jsというnpmモジュールで、ネイティブアプリ開発のようなプリプロセッサによる条件付きコンパイルを行う方法を紹介しました。検証用コードをコメントアウトしたり解除したりしなくて済むようになるのがとても嬉しいですね(一度圧縮するので、ソース内のコメントも本番用JSに残らないのも嬉しいところ)。
自分たちはもちろん、顧客ももっと楽にアプリの確認ができるように、こんな手法を使ってみてはいかがでしょうか。本当にちょっとしたアイデアなのですが、こういったことで少しでもWEBアプリ開発が楽になればと思いました。
FAQ
Q. 条件付きコンパイルをかけたJSって、本当にちゃんと動くの?
A. 基本的には、検証用JSのみならず、本番用JSもどこかのタイミングで確認することが大切です。ただ、自分が使ったバージョンでは、動かなかったという問題がまったく生じませんでした(uglifyjs@2.4.3, grunt-contrib-uglify@0.2.7)。
Q. 出力した検証用JSと本番用JSをどうやって読み替えるの?
A. 検証用WEBサーバーの設定でrewriteを使うのが、一番きれいな方法かなと思います(本番用JSのパスを、検証用JSのパスにrewriteする)。もちろん検証時だけ、html内に記述されているパスを変えても良いのですが、誤ってそのままリリースされる危険があります。grunt でrewrite機能付きのWEBサーバーを立てる方法があるので、こちらが楽です。
https://www.npmjs.org/package/grunt-connect
https://www.npmjs.org/package/grunt-connect-rewrite