モジュールはrequireを実行すると、キャッシュされていればキャッシュを返し、キャッシュになければロードして返されます。
例えばhttpサーバにおいてリクエスト毎にrequireを実行しても毎回ロードが実行されるわけではありません。このモジュールのキャッシュを自由にコントロールできれば自由に読みなおす事ができ、ダウンタイムなしでサーバの更新を行うことも可能です。
#モジュールのキャッシュ
require.cacheにモジュールが含まれます。モジュールのロード時のファイル名がキーとなり、モジュールが値になります。この要素を削除すればrequire時に再度ロードされます。
require("./hello");
require("./hello");
console.log("hello");
この場合、一度しかhelloは出力されません。しかし以下の様なコードでキャッシュを削除すれば二度helloが出力されます。
require("./hello");
delete require.cache[__dirname + "/hello.js"];
require("./hello");
console.log("hello");
#ロードの監視
モジュールのロードが監視できれば、そのキャッシュに使用されるファイル名がわかります。
//モジュールのクラスを取得する
var Module = module.constructor;
//モジュールのプロトタイプのloadを入れ替える
var load = Module.prototype.load;
Module.prototype.load = function(){
//オリジナルのロードを呼び出す
load.apply(this, arguments);
//ファイル名を出力
console.log(arguments[0]);
};
//モジュールをロード
require("./hello");
requireによってModuleのインスタンスが作られload関数が呼び出されますので、これをフックすることでファイル名を取得することができました。
#ロードされたモジュールのファイルの監視
ファイル名が取得できるようになりましたので、fs.watchを使用して自動でロードしたファイルの変更を監視することができます。
変更の通知を受けた時にキャッシュを破棄することで、キャッシュ破棄の自動化が完成します。
var fs = require("fs");
//モジュールのクラスを取得する
var Module = module.constructor;
//モジュールのプロトタイプのloadを入れ替える
var load = Module.prototype.load;
Module.prototype.load = function(){
//オリジナルのロードを呼び出す
load.apply(this, arguments);
//ファイルを監視
var filename = arguments[0];
var watcher = fs.watch(filename);
watcher.on("change", function(){
//キャッシュを破棄して監視を終了
delete require.cache[filename];
watcher.close();
});
};
//モジュールをロード(1秒おきに)
var test = function(){
require("./hello");
setTimeout(test, 1000);
};
test();
これを実行すると、hello.jsが一度だけ読み込まれhelloが出力されます。hello.jsを修正すると再度ロードされるのが確認できます。
(Mac OS XではFSWatcherのchangeイベントが二度発生する事があります)
#応用例
例えばExpressのルータでリクエストを受けた時にrequireでロードしたモジュールに委譲するようにすると、ビジネスロジックの修正をゼロダウンタイムで行うことが可能です。
app.get("/", function(req, res){
require("./handler")(req, res);
});