node-devでrequireしたモジュールを監視できる仕組み
node-devがどういう仕組みでファイルを監視してるのか気になって調べたのでメモ。基本的にはfs.watchFileで監視するんだけど、ロードしてるモジュールも全部監視してるのが謎だった。
node.jsはrequireでモジュールをロードするときrequire.extensionsというのを見て、拡張子毎にコールバックが設定されていて、そのコールバックでモジュールを読み込んでるみたい。
node本体のソース見るとこんな感じ
Module.prototype.load = function(filename) { debug('load ' + JSON.stringify(filename) + ' for module ' + JSON.stringify(this.id)); assert(!this.loaded); this.filename = filename; this.paths = Module._nodeModulePaths(path.dirname(filename)); var extension = path.extname(filename) || '.js'; if (!Module._extensions[extension]) extension = '.js'; Module._extensions[extension](this, filename); this.loaded = true; }; ... // Native extension for .js Module._extensions['.js'] = function(module, filename) { var content = NativeModule.require('fs').readFileSync(filename, 'utf8'); module._compile(content, filename); }; // Native extension for .node Module._extensions['.node'] = function(module, filename) { process.dlopen(filename, module.exports); };
.jsのときは普通にロードして、.nodeのときはdlopenでアドオンモジュールとしてロードされる。なのでこのコールバックを上書きしてやればいい。
というわけでnode-devの実装はこうなってる。
/** Hook into `require()` */ function hookInto(ext) { var extensionHandler = require.extensions[ext]; require.extensions[ext] = function(module, filename) { if (module.id == main) { module.id = '.'; module.parent = null; process.mainModule = module; } watch(module); extensionHandler(module, filename); if (ext == '.js' && Path.basename(filename, ext) == 'coffee-script') { hookInto('.coffee'); } }; } hookInto('.js');
コールバックにフックしてwatch()で各モジュールファイルを監視する。で変更があったらリロードするという感じ。