ファイルを更新したら自動でリロードする仕組みを作った(Node x ChromeExtension)
YeomanとかLiveReloadとかそういうのは色々あるみたいですが、きっと素敵機能が山盛りで使いこなせない気がしたので、必要なとこだけ自作してみました。
それ用のサーバー建てるとか、スクリプト挿入するとか面倒やし。
概要
- Nodeのfsモジュールの類で指定したディレクトリ以下を監視
- ファイルの更新を検知したら、Socket.IOでクライアントへ通知
- Chrome拡張でSocket.IOの通知を受け、ページをリロード
仕組みはシンプルだと思います。
その他に実装したのは、
- 指定ディレクトリ以下を再帰的に監視する
- 除外したいパターンも指定できる
といったところです。
CentOS4系など、Linuxのカーネルが古い場合は、inotify-tools等がインストールされていない影響もあり、fs.watchが使えません。(=本記事の内容が使えない)
この場合、fs.statなどで代用するしかないみたいです。
サーバーサイド
以前の記事で構築したExpressの上で作ったので、Express仕様になってます。
とは言え必要なところを切り出せば全然動くと思います。
依存しているモジュール
- express(おまけ)
- ejs(おまけ)
- socket.io
- fs
- fs-watch-tree
Node標準のfsモジュールは色々イケてないらしいので、fs-watch-treeというモジュールを利用しています。
npmのページの説明は多少古いようなので、Githubの方を要参照です。
このモジュールも根本ではfsモジュールを利用しているそうで、イケてない部分は実はそのままイケてなかったりします・・w
やたらと変更通知するとか・・。
target.json
{ "target": [ "/var/www/node/hoge/", "/var/www/node2/public_html/", "/var/www/php/wp/", "/var/www/piyo/public_html/script/" ] }
絶対パスで監視したいディレクトリを指定します。
このファイルはjsonなので、コメント書くとエラーになります・・。
jsonってしましたが、別にjsファイルでも問題ないぽいです。
app.js
var app = module.parent.exports, io = app.get('io'), fs = require('fs'), watchTree = require("fs-watch-tree").watchTree, consoleDetail = function(e){ var detail = (e.isDelete()) ? 'deleted' : (e.isModify()) ? 'modified' : (e.isMkdir()) ? 'created' : 'changed'; return e.name + ' was ' + detail; }, targetList = require('./target.json').target, excludeList = ["node_modules", "~", "#", /^\./, /^_/], watchObj; // First: Check target dir existence. targetList.forEach(function(d){ fs.exists(d, function(exist){ if(exist){ console.info('Start watching: %s', d); }else{ console.error('Dir %s doesn\'t exist.', d); process.exit(); } }); }); // Second: Client connected, then watch indicated dir. var autoReloaderSocket = io.of('/reloader').on('connection', function(client) { client.emit('connected'); console.info('Client has connected.'); targetList.forEach(function(d){ watchObj = watchTree(d, { exclude: excludeList }, function (e) { console.info(consoleDetail(e)); client.emit('reload'); }); }); client.on('disconnect', function() { watchObj.end(); console.info('Client has disconnected.'); }); });
あとは親のExprssサーバーを起動すればOKという感じ。
クライアントサイド
Extensionの概要
- manifest.jsonで指定した開発環境では、Content Scriptを挿入
- Content ScriptがSocket.IOの通知を受け、ページをリロード
Manifest.json
{ "manifest_version": 2, "name": "Reloader", "version": "1.0", "description": "Reload pages automatically.", "icons": { "16": "icons/icon_16x16.png", "48": "icons/icon_48x48.png", "128": "icons/icon_128x128.png" }, "content_scripts": [{ "run_at": "document_end", "matches": ["http://*.YOURSERVER/*"], "js": ["socket.io.js", "reload.js"] }], "browser_action": { "default_icon": "icons/icon_19x19.png" }, "permissions": ["tabs", "http://*/*", "https://*/*"] }
Content Script
(function(io) { var socket = io.connect('http://YOURNODESERVER/reloader', {port: 9999}), isReloading = 0; socket.on('connected', function() { console.log('Waiting for updates...'); }); socket.on('reload', function() { if(isReloading){ return; } isReloading = 1; console.log('Reloading.') location.reload(); }); }(io));
isReloadingは、通知がやたらと飛ぶイケてないfs.watchのためです。
未達、不満など
excludeできてない
勉強不足なせいだとは思いますが、除外の指定をしているにも関わらず、普通に通知されたりします。
なんでやろ・・。
excludeListもtargetみたく外出ししたかった
正規表現のとこが引っかかって、外から呼べなかったんです。
どうにかならんかなあ。
watchしてるファイルの変更通知がやたら飛ぶ
fsモジュールのイケてないところだそうです。
どうしようもないのでクライアントサイドで間引いてます。
その内改善されると思ってます。
Content ScriptのOn/Offとかしたかった
今だとmanifest.json内でしか設定ができません。
ただContentScriptは後からOn/Offできるものではないそうなので、どうすりゃいいのかしら。
公開したかった
Githubとか、ChromeStoreとか。
でもちょっと納得できない出来なので、ブログに書くにとどめます・・。
とは言え個人で使う分には十分なので、今のとここのまま使ってます。
意外と便利です。