とある角度から

お腹いっぱいたべられる幸せ

RequireJSでモジュール管理してみるサンプル

RequireJSとはf:id:n_1010real:20140509205117p:plain

Javascriptモジュールローダの1つです。
モジュールの依存管理だけではなく、パフォーマンス面も考慮されたライブラリで、
開発効率とパフォーマンスを両立したJavascript開発を実現できるかも。。。

具体的な特徴

特徴、というか全部メリットですね。

  • モジュールのカプセル化
    • RequireJSではAMDモジュールといわれるカプセル化された再利用しやすい記述が可能
    • AMDモジュールはグローバル領域に頼らずに他のモジュールを呼び出すためグローバル領域を汚染しにくい
  • モジュールの依存関係の管理
    • 依存関係から勝手に順番を考慮してスクリプトを読み込んでくれるので、ユーザは読み込み順を意識する必要がない
  • パフォーマンス向上が見込める
    • 非同期読み込みによるレンダリング速度の向上
    • 最適化ツールによるロード量・コネクション減少


では、サンプルを用いて、RequireJSを使うとどうなるかを観ていきます。

RequireJSを使わないとこう成り得る...というサンプル

ページのロードが完了したら、タイトルと説明文をページに表示するというサンプルを用意してみました。
敢えて、色々良くない書き方をした部分もありますので、参考にはしないでください。

動作イメージ(in plunker)


ファイル構成
  • index.html - メインHTML
  • site-common.js - サイト内共通で使う定数や関数をここで定義
  • addon.js - 上記のsite-common.jsの機能を拡張するようなモジュール
  • page.js - ページのメインコード
各ソース

index.html

site-common.js

addon.js

page.js

あと、このサンプルですが、以下のシナリオで作られたと想定してみてください。

  1. 最初はシンプルに、ページロード完了時にタイトルと説明を表示していた。
  2. 後になって、別の開発者がaddon.jsを追加し、page.jsも書き換えて、タイトルに鍵カッコを付けた。
問題点

複数開発者で開発することを考慮すると、とたんに色々な問題点が出てきます。

  • ユーザがライブラリの読み込み順を知らないといけない

addon.jsとsite-common.jsの読み込み順を入れ替えると鍵カッコがundefinedになってしまいます。
読み込みの順番が変わると正常動作しないってことはそれを全て把握して読み込む必要が出てきます。めんどい。

  • グローバル汚染

定義している変数・関数が、全てグローバル領域に...汚染がひどいです。constantという名前で他の人が変数を定義したら色々死亡します。

  • 意図と外れた設計

そもそもconstantへのアクセスは、site-common.jsを見る限り、setConstant,getConstantを用いて行って欲しいという意図が汲み取れますが、addon.jsの開発者がそのことを知らないのか、意図しないconstantへの直接アクセスが行われています。


という訳で、早速RequireJSさんにおでましいただきます。

RequireJSを適用してみる

適用してみた

動作イメージ(in plunker)


動作は同じですね

各ソース

index.html - 修正後
まずは、サイトからrequirejsをDLして設置。
スクリプト読み込みは、このrequire.jsのみに変更します。
data-main属性には、このページのメインコードのみ指定します。
また、scriptタグにasync属性を追加すると、対応ブラウザにおいて非同期でjsを読み込むことができます。

site-common.js - 修正後
まず全体をdefine(function(){ ・・・ })でくくります。
次に関数の戻り値として、このモジュールで公開したいオブジェクトのみを記述します。(return文)

addon.js - 修正後
全体をdefineでくくる際に、第一引数に依存モジュール(ここでは'site-common.js')を指定します。
また、site-common.jsモジュールのアクセスするための引数(名前は何でも良い。ここではCommon)を指定します。
その他は、site-common.jsと同じです。

page.js - 修正後
defineではなくrequireでくくります。
このrequire内部が、ロードが完了した際に実行されますので、window.onloadの内部処理をそのまま書けば大丈夫です。
また、define同様に依存モジュールとその引数も定義します。

考察

RequireJSを適用した事で、どんなメリットが生まれたかを列挙してみます。

  • 読み込み時に、順番を意識しなくて良い

読み込みの順番はRequireJSが勝手に解釈してくれるので、各モジュールの依存関係さえ間違っていなければ、意識する必要はありません。
複数人での開発時にも、自分がそのページでやりたいことを書いたファイル(ここではpage.js)をロードすれば問題ありません。

  • グローバルが汚染されていない

RequireJSを使うと、defineでくくり、公開する内容を自分で指定できるので、自然とグローバル領域は自然と汚染されない書き方になります。
(グローバルへの定義はRequireJSの提供するrequire関数、define関数のみとなっています)

  • constantがプライベートに

addon.jsでのconstantへの直接アクセスもエラーとなり、公開されたsetConstantメソッドで書き換えるしかなくなりました。
これにより、site-common.js設計者の意図が守られます。

RequireJS使用前と使用後のネットワーク状態を比較してみます。
使用前
f:id:n_1010real:20140509174409p:plain
使用後
f:id:n_1010real:20140509174414p:plainスクリプトのローディングを非同期で行うことにより、DOMの描画が並行で行われています。
結果的にDOMContentLoadedのタイミングはかなり早くなりました。
スクリプトの読み込みが終わらなくても、ユーザの目にサイトが表示されるので、待ち時間がより少ないと感じるはずです。

これらのメリットは、開発規模が大きければ大きいほど、開発人数が増えれば増えるほど、大きな効果をもたらすと思います。

さらに

RequireJSには、最適化ツール(r.js)を用いて、各モジュールをminifyし結合することができます。
これにより、さらなるパフォーマンス向上も見込めます。
そこについては、またその内。。。