Edited at

自作ライブラリをrequreされてもそうでなくても使えるようにする

More than 3 years have passed since last update.

こないだは自作ライブラリを Browserify でモジュール管理する場合に、その自作ライブラリを書き換えるやり方を書いたけど、そのままだと Browserify を使わない場合にはそのモジュールを使うことが出来なくなる。

それはそれで辛いので、Browserify を使う場合でも、以前のやり方のようにhtmlのスクリプトタグで読み込む場合でもどちらでもライブラリ自体の書き換えなしに使えるようにしようと思う。使う時のことも考えて作るってのが男ってもんだよね。


基本方針


Browserify


  • 他で利用するオブジェクトを exports に入れる

  • ライブラリが他ライブラリに依存する場合は require する


ブラウザ


  • 他で利用するオブジェクトをグローバル変数に入れる

  • ライブラリが他ライブラリに依存する場合はグローバル変数に入ってるものを使う

この方針で色々な書き方を試していたんだけど、結局現在主流っぽい書き方がやっぱり秀逸で、もうこれで良いじゃんってなったのでそれにならうことにした。先人はマジ凄い。

代わりに、何故そういう書き方になったのかを解説していこうと思う。


大枠

( function ( definition ) {

// ここに使われ方別の定義を書く
} )( function ( root, $, _ ) {
//ここにモジュールのコード本体を書く
} );

まずはこういう大枠を作る。これはよくある形の変形型だと思えば良い。

( function () {

// ここにコードを書く
} ) ( );

ここから軽く触れていくことにするけど、これは実際こういう意味。

( function ( ) { } )

ここで定義した関数を

( function ( ) { } ) ( );

実行する

なので、定義してすぐ実行するから即時関数とか呼ばれたりする。

そして、この関数はもちろん関数なのだから引数を使う事ができる。

( function ( w ) {} ) ( window );

これは、「この関数は w という引数を取る。そして、その引数に入ってる値は window オブジェクトだ」っていみになるよね。

そしてこの実行時に引数として渡す部分をモジュールの本体に置き換えてみる。

( function ( mod ) {} ) ( function ( ) { } )

これでモジュール本体は mod に格納されていることになる。


モジュール本体の記述

そろそろ見やすいとは言い難くなってくるので改行を追加してみる。

( function ( mod ) {

} ) ( function ( ) {
var modA = function () {
// モジュールの本体
};
return modA; // 巻数実行時にはモジュールを返す
} );

モジュール本体の関数は、返り値としてモジュール自体を返すようにすれば、1行目の mod の中身がモジュール自体になる。

あとは、最初の関数の中でモジュールを返す関数を実行してあげれば良い。

( function ( mod ) {

var modA = mod(); // これでモジュールが作られる
} ) ( function ( ) {
var modA = function () {
// モジュールの本体
};
return modA; // 巻数実行時にはモジュールを返す
} );


モジュールの使用用途別の定義

そうやって作られたモジュールを、後は使用される用途にあわせてグローバルオブジェクトに突っ込むか module.expors に突っ込むかを書いてあげる。

( function ( mod ) {

// browserify ( commonJS )
if ( typeof exports === "object" ) {
module.exports = mod();
} else {
window.modA = mod();
}

} ) ( function ( ) {

var modA = function () {
// モジュールの本体
};
return modA; // 巻数実行時にはモジュールを返す

} );


そのモジュールが他モジュールに依存している場合

今までの記述で問題ないように思えるんだけど、実は問題が一つ残っている。それは、モジュール本体が別のモジュールに依存している場合の記述が抜けている。今回の例だったらjQuery。

解決法としては、モジュール本体にまた if文書いて Browserify だったら requireする、とかにしても良いんだけど、環境別の記述が2箇所になるのは避けたい。

なので、モジュールの環境別定義部分の if 文の中でそのモジュールが依存しているモジュールに関しても解決をしてしまうとスマートな記述になる。

( function ( mod ) {

// browserify ( commonJS )
if ( typeof exports === "object" ) {
var $ = require( 'jquery' );
module.exports = mod( $ );
} else {
window.modA = mod( window.$ );
}

} ) ( function ( $ ) { // 引数に定義箇所で指定した依存モジュールを指定する

var modA = function () {
// モジュールの本体
};
return modA; // 巻数実行時にはモジュールを返す

} );

実際にやることは、定義部分で呼び出している関数に引数を与えて上げれば良いだけ。 Browserfiy であれば require したものを引数に渡してあげれば良いし、そうじゃない場合は window オブジェクトに既に変数が入っているはず ( この場合であれば window.$ か window.jQuery )ので、それを引数に渡してやる。

これで大体今まで作ってきたモジュール達がBrowserifyに対応したことになる。