RequireJSにはこんな機能もあった

この記事はJavaScript - Client Side - Advent Calendar 2013 17日目の記事です。

JavaScriptでモジュール管理を実現できるRequireJSですが、どんくらい使われてるのでしょうかねー?初めて触ったときは、すげー!って思ったのを覚えています。
でも、試しに使ったことはあったものの、最近になって初めてちゃんと使いました。周りでもあんまり使ってる人見ないような・・・、普段はバックエンドの仕事をしているせいかな・・・。
それで、RequireJSのドキュメントを読んでたわけですが、こんな機能もあったのかぁ、ということがあったので書きます。
RequireJSがどんなものかはWeb上に情報がたくさんあるので割愛します。

使用したRequireJSのバージョンは、2.1.9です。

requireメソッドを使って、Consoleデバッグ

最初はrequireメソッドを使うとConsoleデバッグできますよ、というお話。
公式サイトの説明はこちら -> http://requirejs.org/docs/api.html#modulenotes-console

さて、RequireJSでは、別のモジュールに依存しているモジュールを書く場合は以下のように書く。

define(['mod1', 'mod2', 'mod3'], function(mod1, mod2, mod3){
  // ・・・
});

define()の第1引数に依存するモジュール達を記述しておくと、第2引数の関数の引数にそのモジュールが達が渡される。モジュールの記述順と引数の順番を揃える必要があるので、依存するモジュールが増えると管理しずらくなる。
そこで、require()メソッドを使うと以下のように書くことができる。

define(['mod1', 'mod2', 'mod3'], function(){
  var mod1 = require('mod1'),
      mod2 = require('mod2'),
      mod3 = require('mod3');
  // ・・・
});

これで、引数の順番とか気にする必要がなくなった。

ここからが本題で、このrequire()メソッドをブラウザのJavaScriptコンソールで使うことで、モジュールを取得できる。

> var mod1 = require('mod1');
> mod1.hoge();

こんな感じで、モジュールをコンソールから自由に使えるようになるので、デバッグがしやすくなる。これは便利そう!

JSONP

お次は、JSONPで取得したJSONをRequireJSのモジュールとして扱う機能。
公式サイトの説明はこちら -> http://requirejs.org/docs/api.html#jsonp

以下のように依存するモジュール名ではなく、JSONPに対応したAPIURIを指定しコールバック関数をdefineとしておくだけ。

define(['http://localhost/api.json?callback=define'], function(data){
  // ・・・
});

これだけでできちゃうんだけど、JSONPを使うってことは外部サービスに依存することになると思うのでエラー時の処理がより大事になる。
APIのレスポンスコードが200なんだけどメッセージがJSON形式でない場合、上の例だとdataundefinedになる。

APIのレスポンスコードが404や500の場合、RequireJSの例外が発生する。このエラーをハンドリングしたい場合はdefineの第3引数としてエラーハンドリング用の関数を定義する。

define(['http://localhost/api.json?callback=define'], function(data){
  // ・・・
}, function(err){
  console.log(err); // {requireType: "scripterror", requireModules: ["http://localhost/api.json?callback=define"], originalError: Event}
});

200でメッセージがJSON形式でない場合もエラーハンドラで処理したい場合は、configでenforceDefinetrueに設定すれば例外を発生させることができる。

require.config({
  enforceDefine: true
});
define(['http://localhost/api.json?callback=define'], function(data){
  // ・・・
}, function(err){
  console.log(err); // {requireType: "nodefine", requireModules: ["http://localhost/api.json?callback=define"]}
});

こんな感じでJSONPで取得したJSONをモジュールとして扱うことができることが分かった。

プラグインを使ってi18n対応

RequireJSのプラグインを使って国際化対応ができる。
公式サイトの説明はこちら -> http://requirejs.org/docs/api.html#i18n

プラグインは以下のページからダウンロードできる。
http://requirejs.org/docs/download.html#i18n

まず、ダウンロードしたi18n.jsをdata-mainに指定するjsファイルと同じディレクトリへ配置。ここでは、$PROJECT_ROOT/public/jsに置くとする。
次にnlsというディレクトリを作成し、その中に国際化対応したい文字列を定義したモジュールを作成する。rootというプロパティに定義したい文字列を定義していく。
例えば、色の名前を定義したcolorsモジュールは以下のような感じ。

// $PROJECT_ROOT/public/js/nls/colors.js
define({
  root: {
    red: "red",
    blue: "blue",
    green: "green"
  }
});

今度は、colorsモジュールを使う側のコード。colorsモジュールを指定するときにプレフィックスとして"i18n!"をつける。そうすると、i18nプラグインを利用してcolorsモジュールを読み込むことができる。

// $PROJECT_ROOT/public/js/main.js
require(['i18n!nls/colors'], function(data){
  console.log(data); // {red: "red", blue: "blue", green: "green"}
});

これでdataに、colorsモジュールのrootプロパティに定義したオブジェクトがセットされる(i18n!を付かなかった場合は通常通りdefineに指定したオブジェクトがセットされる)。

じゃあ、colorsモジュールを国際化してみる。日本語版をcolorsモジュールを作ってみる。nls/colors.jsに、ja: trueを追加。

// $PROJECT_ROOT/public/js/nls/colors.js
define({
  root: {
    red: "red",
    blue: "blue",
    green: "green"
  },
  ja: true
});

日本語の色名をnls/ja/colors.jsに定義する。

// $PROJECT_ROOT/public/js/nls/ja/colors.js
define({
  red: "赤",
  blue: "青",
  green: "緑"
});

ブラウザの言語設定が日本語にしておくと、以下のようにdataには、nls/colors.jsではなく、jaディレクトリに作ったnls/ja/colors.jsで定義したオブジェクトがセットされる。

require(['i18n!nls/colors'], function(data){
  console.log(data); // {red: "赤", blue: "青", green: "緑"}
});

こんな感じで国際化対応ができた。

ただ、r.jsでファイルを結合した場合、うまく動かない・・・。
nls/colors.jsは結合後のjsファイルに含まれるが、nls/ja/colors.jsは含まれないみたい。そのため、nls/ja/colors.jsが必要な時は、結合していない時と同じようにRequireJSにより非同期ロードが行われる。この時のリクエストURLが、/public/js/nls/ja/colors.jsではなく、/nls/ja/colors.jsになってしまい、nls/ja/colors.jsが404エラーになり正常に動かない。

以下のようにpathsの設定を使えば対応できるけど、言語毎に設定するのは面倒だなー。何かいい方法があるのかも。

require.config({
  paths: {
    "nls/ja": "public/js/nls/ja"
  }
});
require(['i18n!nls/colors'], function(data){
  console.log(data); // {red: "赤", blue: "青", green: "緑"}
});

サーバーサイドからデータ取ってくる場合はサーバーサイドで国際化対応すればいいと思うけど、クライアントサイドのみで動かしているアプリケーションを国際化対応したい場合は便利かもなー。

おしまい

コンソールデバッグJSONPi18nの3つを使ってみました。
プラグインを見ると色々ありそうでしたけど、RequireJSでやる必要ないんじゃない、とか利用シーンが思いつかないものが多かったです。
とはいえ、JavaScriptでモジュール管理できるのはうれしいので、RequireJSを使い込んでみたいなぁと思っている次第でございます。