RequireJSをプロジェクトで使ってみての所感

JavaScriptのモジュール定義・読み込みをAMD (Asynchronous Module Definition) でできるライブラリであるRequireJSをプロジェクトで使ってみた。

プロジェクトの規模感を簡単に説明しておくと、まず単画面アプリケーションではなく、従来通りテンプレートが複数あるウェブサイトだ。よくあるウェブサイトとはいえ、JavaScriptで動く機能やUIがたくさんあり、テンプレートのパターンが100枚以上ある。カルーセルと画像切り替えが動けばヨシ、みたいなそこらのコーポレートサイトよりは遥かに規模の大きいウェブサイトとなっている。

導入を決めたのは、今回のプロジェクトで必ず現出するであろう懸念点を、あらかじめ潰しておきたかったという動機から。その懸念点というのは主にファイルサイズの問題。ウェブサイト内で使われている全JSファイルを全て結合圧縮すると、最終的に1MBくらいになってしまい、初回ロード時に読ませるにはちょっとキツい。可能なら、使用されるページが限定されているJSファイルは、必要なときにだけリクエストされるようにしたかった。

そういう意味では、今回の導入の動機は“ファイルサイズ、リクエスト数の最適化”であって、“モジュール管理”ではない。という前提がありますね。

RequireJSを使って良かった点

  1. まともにモジュール管理ができるようになった
  2. コンパイルが強力。Gruntとの相性が良かった
  3. 各ページで読み込まれるJSファイルを3ファイル以内に抑えられた

RequireJSを使ってイマイチだった点

  1. scriptタグをHTMLに埋め込むと厄介
  2. 稀になかなかJSが実行されない

以下、詳細。

まともにモジュール管理ができるようになった

JavaScriptは言語仕様としてモジュールの仕組みを持っていないため、モジュール管理的な事をしようと思ったら自前で用意しなければならない。たいていの場合、ルールとしてモジュール名やオブジェクトのツリー構造を決めていくことになる。RequireJSを導入することによって、1ファイル=1モジュールと半ば強制的になる。そのためツリー構造はフォルダツリーを見れば把握できるようになる。

それから、依存関係を明示することで、そのモジュールの正体が把握しやすくなった。JSの読み込み順を気にする必要もなく、無用なストレスから開放された。

コンパイルが強力。Gruntとの相性が良かった

RequireJSを使うと、1モジュール=1ファイルになるため、ファイル数がドーンと増える。あわせてリクエスト数もドーンと増えてしまうのでは? という懸念があるが、提供されているオプティマイザーを使うと、依存しているファイルを再帰的に探って1ファイルにまとめてくれる。このオプティマイズのタスクをGruntに任せることができて、楽だった。

「全部のページでモジュールA,B,Cは読み込ませたい。モジュールD,Eは特定のページでしか使わない」という場合にも、オプティマイザを使うことで「モジュールA,B,Cを含むABC.js」と「モジュールD,Eを含むDE.js」に別々に結合・圧縮することもできる。モジュールDがモジュールAに依存していたとしても、DE.jsにはAを含まないように、よしなにやってくれたりもする。

このやり方はまた別のエントリーにするかもしれない。

各ページで読み込まれるJSファイルを3ファイル以内に抑えられた

前項のコンパイルを施すことで、①全ページ共通のJS、②画面固有のJS、③RequireJS本体 の3ファイルにまとまった。

scriptタグをHTMLに埋め込むと厄介

RequireJSに則ってモジュール定義したものを、HTMLの中から直接scriptタグを使って埋め込もうとするとハマる。非同期読み込みなので、scriptタグが登場した時点でそのモジュールが存在している保証がないためだ。

そもそもどういう機会にscriptタグをHTMLに直接記述するかというと、例えば画像切り替えのJSを即時発動したい時。よくある書き方は以下のような感じ。

$(function () {
    $('.image_switcher').imageSwitcher();
})

シンプルでわかりやすいな発動方法だが、DOM Readyのタイミングまで発動されないのが欠点。そのため、画像をクリックしても何も起きないタイミングが存在してしまうことになる。そこで、scriptタグを使って記述すると、

<div class="image_switcher" id="image_switcher_1">
    <!-- 画像が何枚か並んでる -->
</div>
<script>
    $('#image_switcher_1').imageSwitcher();
</script>

となり、該当箇所のDOM構築が終わった直後にJSを発動するので、画像をクリックしても何も起きないタイミングがほぼ無い。

RequireJSを使用していると、上の例でscriptタグが登場した時点でそのモジュールが存在している保証がないため、こういう細かいパフォーマンス調整は出来なくなってしまう……。

稀になかなかJSが実行されない

予想以上にスクリプトのリクエストが遅延して、なかなかJSが発動されないケースがある。ブラウザにもよるけど、ブラウザは同時に8リクエストくらいしかできない。その帯域をCSSや画像などで埋められていると、JSのリクエストが後回しになり、そのぶん実行が遅れる。結果的にJSの実行がDom Readyよりずっと後になってしまうこともある。

総評

少なくとも、 RequireJS は、 Sass のような「とりあえず入れとけばよい」系のプロダクトではないと思った。入れるべきか、控えるべきかを切り分けるキーになるものは、プロジェクトの規模なのかなー。ある程度の規模になると、モジュール管理の機構をなくしてコードを書くのが困難になる。スクリプトの結合・圧縮もスマートに行いたくなる。このあたり、必要のない程度の規模のプロジェクトであれば、わざわざ導入しなくてもよいかもしれない。

特に、JSの実行が大幅に遅れてしまうことが稀にある問題は、結構なネックとなっているとおもう。仕組み的に仕方ないとはいえ、これがあるから導入を見送るという考え方は全然アリだと思う。