Browserify + gulp-uglify で JS ファイルを連結 & minify - Gulp で作る Web フロントエンド開発環境 #3

browserify-wizard

Ruby on Railsや Middleman における開発では、 sprockets によって複数ある JS ファイルを一つに連結して依存関係を解決するのが一般的かと思います。今回は Gulp での開発環境を作るということで、それ以外の方法で依存関係を解決することになります。そんな訳で、Gulp で依存関係を実現するモジュールである Browserify を試してみるとします。

サンプルコードはこちらからどうぞ。

そもそも依存関係って何?

Browserify とはJSファイル間の依存関係を管理することが出来るライブラリです。

なんて解説をよく見かけますが、日本語力の低い僕は依存関係なんて単語をいきなり出されてもよくわからないので、もっと身近な具体例を挙げて理解を深めるとします。

例えば jQuery を使った JavaScript コード (main.js) を貴方が書いたとします。当然そのコードが動作するには、既に jQuery が読み込まれていなくてはならなりません。つまり HTML にこう記述する必要があるわけです。

<script src="js/jquery.js"></script>
<script src="js/main.js"></script>

main.js は jquery.js が先に読み込まれていないと動作しません。ということは main.js は jquery.js なしでは生きていけない。すなわち main.js は jquery.js に依存していると言えます。これが依存関係です。で、その依存関係とやらをコードに表したのが上の HTML コードなのです。要するにこの依存関係を解決するというのは、 main.js が動作するのに必要な外部ファイルが何であって、それらをどういった順序で読み込めばよいのかというのを解決することなのです。

ここまでで依存関係については理解出来ました。で、これの何が問題かというと、読み込む JS ファイルが増える度に HTML に script タグを追記する必要があるということです。それ自体が手間ですし、JS の依存関係にもかかわらず直接は関係ない HTML にそれが記述されているというのもよろしくありません。JS に関する情報なんだから、JS 内に依存関係を定義出来るようにしたいわけです。

ところで Node.js でサーバーサイドの JS コードを書くとき、よく以下のようなコードを目にします。

var sub = require( './sub.js' );
// 以下、sub を利用したコード

main.js の中で sub.js の機能を使いたいときは、 上記のようにrequireという関数を使って丸ごと読み込んでしまうことで、あたかも外部ファイルであるはずの sub.js 内のコードが main.js 内に記述されているかのように呼び出すことができるようになります。これをブラウザ上の JS でも実現するのが Browserify というライブラリです。

<script src="js/app.js"></script>

Browserify によって、main.js と sub.js の依存関係を全て解決したapp.jsというJSファイルを新たに生成します。HTMLから見れば、このapp.jsというJSファイルだけを常に読み込めば良いことになります。たとえ JS 側で依存関係が変わったとしても、最終的には app.js という形で生成されることに変わりはないのだから、HTML 側を修正する必要はありません。

ダラダラと説明が続きましたが、要するに Ruby でいうところの sprockets だと思っておけば OK (たぶん)。

まとめます。

  • 複数の JS ファイルを 1 つのファイルに連結する
  • メインとなる JS ファイルが参照したい外部モジュール(ファイル)を読み込むのという形を取る
    • つまり読み込む順番で外部モジュールが連結され、最後にメインの JS ファイルが連結されてひとつのファイルが生成される
  • HTML 側は常に Browserify によって生成された JS ファイル一つだけを読み込めば OK

Gulp を使って Browserify を体験してみる

とりあえず Browerfiy がどういった事をするのかを手軽に体験するための簡単なサンプルを作ってみるとします。最初なので AltJS も使わずに素の JavaScript を一つのファイルに連結してみましょう。

project/
  ├── dest/
  │   └── js/
  │       └── app.js
  └── src/
      └── js/
          ├── main.js
          └── sub.js

src/ディレクトリ内にある JS ファイルが開発用で、これらを Browerify で一つに連結してapp.jsというファイルを生成します。

必要な Node パッケージをインストールします。

$ npm i -D browserify
$ npm i -D vinyl-source-stream

適当な JS ファイルを用意します。

var sub = require('./sub.js');
sub('hello, world');
module.exports = function(message) {
  alert(message);
};

タスクを定義します。

var gulp       = require('gulp');
var browserify = require('browserify');
var source     = require('vinyl-source-stream');
gulp.task('browserify', () => {
    return browserify({
        entries: ['./src/js/main.js']
        })
        .bundle()
        .pipe(source('app.js'))
        .pipe(gulp.dest('./dest/assets/js/'));
});

entriesというプロパティに生成対象となるファイルを指定します。配列形式で複数指定することができ、この指定した順番に連結されます。

気になるのがvinyl-source-streamですが、これは npm 全般で使われるオブジェクトを取り扱うパッケージです。自分もそこまで詳しく理解できていないので、詳しい説明は割愛します。Gulp で定義するタスクは、pipe という関数に様々なオブジェクトを引数として渡しますが、ここで渡されるオブジェクトは vinyl という形式(?)である必要があります。browserify は bundle() という関数でファイルストリームを返しますが、vinyl ではないので変換してあげなくてはなりません。そこで登場するのが vinyl-source-stream なわけです。source() にソースとなるファイル名を渡すことで vinyl に変換されたオブジェクトが返ってきます。

実行してみます。

$ gulp browserify

生成された app.js がこちら。

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var sub = require('./sub.js');
sub('hello, world');
},{"./sub.js":2}],2:[function(require,module,exports){
module.exports = function(message) {
  alert(message);
};
},{}]},{},[1]);

一行目に require 関数が定義されています。それ以降に main.js と sub.js が連結されています。しかも全体が関数スコープで囲まれているので、グローバル汚染の心配もありません。とりあえずはこれで OK でしょう。

gulp-uglify で JS ファイルを minify 化してみる

gulp-uglify は JS ファイルを minify 化することが出来るパッケージです。

パッケージをインストールします

$ npm i -D gulp-uglify

タスクを定義します。

var uglify = require('gulp-uglify');
gulp.task('compress', () => {
    return gulp.src('./dest/assets/js/*.js')
        .pipe(uglify())
        .pipe(gulp.dest('./dest/assets/js/'));
});

やることがシンプルなだけにタスクの定義も凄く簡潔ですね。

実行してみます。

$ gulp compress

minify 化された JS がこちら。

!function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var s=n[i]={exports:{}};e[i][0].call(s.exports,function(r){var n=e[i][1][r];return o(n?n:r)},s,s.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}({1:[function(r,e,n){var t=r("./sub.js");t("hello, world")},{"./sub.js":2}],2:[function(r,e,n){e.exports=function(r){alert(r)}},{}]},{},[1]);

いい感じですね。