フロントエンドJavaScriptでもrequireを使ってモジュールを分割する

  • 74
    Like
  • 0
    Comment
More than 1 year has passed since last update.

最近、今まで色々と書いてきてた WebフロントエンドUIの 各モジュールファイルをあらかた書き換えた。別に機能を増やしたりしたわけでないんだけど、最近の JS のモジュール化の動きで今までの方針でもモダン(って良い方があってるかどうかは知らんけど)な方針も使えるように変更したので、その経緯とか結果とかを残しておこうと思った次第です。

今までの書き方は基本的にプロジェクト毎にユニークなグローバルのオブジェクトを作ってそこに各UIのオブジェクトをプロパティとして生やしていくという多分それなりにスタンダードだった方針を取ってた。

// moduleA.js
var  app = app || {};

(function(w, $, app){
    var moduleA = function(){
        // モジュールを記述
    };
    app.moduleA = moduleA; // プロジェクトグローバルにモジュールを生やす
})(window, jQuery, app);

これがここ数年でガラッと変わったのでそこら辺についてまず。

JavaScriptにはモジュール化の仕組みがない

そもそも、JavaScriptにはコードをモジュール化してそれを必要な際に呼び出して使用するような仕組みが言語仕様として存在しない。

よほど小さいプログラムでも無い限り全ての処理を1ファイルにまとめたいという強い意志を持ち続けられる人はそう多くないはずだ。たいていの人は処理毎とかにファイルを分割して、必要な際に必要なファイルを呼び出して使う、という方針で開発を進めたいと思うものだと思ってる。

でも、そういう誰もが欲しいと思うような機能が実は JavaScript では実現できていなかったりする。

じゃあどうやって複数のファイルにまたがった処理を書くんだよ!ってなるんだけれども、それをブラウザ環境ではグローバルオブジェクト(ブラウザなら window オブジェクト)にプロパティを作って、それを使い回すという割と強引な手法が長らく使われてきた。

その影響でよく jQuery のバージョンが違うファイルが複数回呼ばれててエラーになってたりしたよね。

NodeJS では require が使える

ただ、サーバサイドとしての JavaScript である NodeJS ではその辺が解消されていて、require という関数を使えば外部ファイルを読み込めるようになっている。

// a.js
var moduleA = {
    init: function(){
        console.log('run init on moduleA');
    }
};
module.exports = moduleA;


// main.js
var a = require('./a');
a.init();
// run init on moduleA;

これ凄く羨ましいと思った記憶がある。

Browserify でブラウザにも require を

その羨ましい手法を使えるようにしたのが Browserify。Browserify で JS をビルドすれば NodeJS のように requre が使えて、モジュールを分割した書き方が出来るようになる。これでもう尋常じゃない数のスクリプトタグや gulpfile 内に何個も何個もモジュールを書き足してその度に gulp コマンドを再実行することがなくなる。

browserifyの使い方

browserify は npm でマシンにインストールする

npm i -g browserify

インストールすれば browserify コマンドが使えるようになるので、コマンドを叩いてJavaScriptファイルをビルドする。

browserify main.js > bundle.js

これで main.js を bundle.js にビルドする。

ビルドする際、 main.js に記述されている依存ファイル達も一緒に含んだ bundle.js ファイルが生成される。そのためファイルサイズが大きいライブラリを呼び出している場合は bundle.js がかなり大きなサイズになってしまう。たとえば jquery と underscore を呼び出している場合はそれだけで1万行にもなる。

gulpでBrowserifyを使う

これで require が使えるようになったわけだが、この時点ではとりあえず2点ほどの弱点が存在する。1点は既存ビルド環境との兼ね合い。もう一つはビルドのかかる時間だ。

ここ数年のWebフロントエンドを書いている人はもう何かしらのビルドツールを使って css、js をコンパイルするのが当然という風潮があると思うので、例えば gulp の watch で JS を concat してたり ugilfy してたりするんじゃないかと思うが、そうであるなら browserify も是非ともそのビルドシステムに混ざって欲しいものだ。

そこで、browserify を(ここでは僕が使ってるため)gulp からビルド出来るようにしようとするだろう。もちろん gulp-browserify は存在するし、各々の JS ファイルを watch していればちゃんと今まで通りビルドしてくれる。

ただ、Browserify を使う場合は gulp の watch は使わない方が良いため、gulpfile.js の記述は後述とする。

watchifyで差分ビルド

gulp の watch を使わない方が良い理由として、先ほど上げたビルドする時間の長さが挙げられる。

Browserify はその特性上ライブラリも全て1つのファイルとしてコンパイルするため、各 JS ファイルを保存する度に一からファイルをコンパイルしていれば必ずそれがストレスの元になる程度には遅い。

それを解消するためには、gulp の watch ではなく watchify を使えば良い。watchify でビルドした場合は変更があったファイルのみの差分ビルドを行ってくれる。

なので、方針としては JS のビルドは watchify に任せて css とか html のビルドを gulp watch でやれば良い。

以下、watchify を使う場合の gulpfile.js

var gulp = require("gulp");
var connect = require('gulp-connect');
var browserify = require('browserify');
var buffer = require('vinyl-buffer');
var rename = require('gulp-rename');
var watchify = require('gulp-watchify');
var sourcemaps = require('gulp-sourcemaps');
var uglify = require('gulp-uglify');

// local server
gulp.task("connect", function(){
  connect.server({
    port: 3000,
    livereload: true
  });
});

// js
gulp.task('watchify', watchify(function(watchify){
  gulp.src("./src/js/main.js")
    .pipe(watchify({
      watch: 'on',
      debug: true
    }))
    .pipe(rename({
      basename: "bundle",
      extname: ".js"
    }))
    .pipe(gulp.dest("./js/"));
}));

// default task
gulp.task("default", ['watchify'], function(){
  gulp.start(["connect"]);
});

これで一通り便利に JavaScript で require を使えるようになった。

では次記事で、requre を使った場合は今までの書き方からどういう風に書き換えれば良いかについて書こうと思う。

次:JavaScript - 既存 JS ファイルを Browserify 用に書き直す - Qiita