Comparing version 0.1.3 to 0.1.4
195
dweb.js
@@ -6,98 +6,123 @@ #!/usr/bin/env node | ||
// | ||
var fs = require('fs'), | ||
argv = require('optimist').argv, | ||
path = require('path'), | ||
util = require('util'), | ||
connect = require('connect'), | ||
inotify = require('inotify').Inotify, | ||
socketio = require('socket.io'); | ||
// WARNING: THIS IS NOT A PRODUCTION WEBSERVER. | ||
// USE FOR DEVELOPMENT ONLY. | ||
// | ||
// In particular, URLs are not mapped to files in a secure way. | ||
// | ||
var fs = require('fs') | ||
, argv = require('optimist').argv | ||
, path = require('path') | ||
, util = require('util') | ||
, walk = require('walk') | ||
, connect = require('connect') | ||
, inotify = require('inotify').Inotify | ||
, socketio = require('socket.io') | ||
; | ||
(function main() { | ||
// Start webserver serving out current working directory | ||
// Parameters | ||
var watch_extensions = { | ||
'.html': true, | ||
'.js': true, | ||
'.css': true, | ||
'.less': true, | ||
'.json': true | ||
}; | ||
argv.port = argv.port || 8000; | ||
argv.dir = argv.dir || process.cwd(); | ||
argv.poll = argv.poll || 10000; // poll frequency for new files, in milliseconds | ||
// Recursively add inotify watches for files in directory | ||
var monitor = new inotify(); | ||
var watched_files = {}; | ||
(function _walk_directory() { | ||
var walker = walk.walk(path.normalize(argv.dir)); | ||
walker.on('file', function(dirName, fileStats, next) { | ||
var filename = fileStats.name; | ||
var extension = path.extname(filename); | ||
if (watch_extensions[extension]) { | ||
var filepath = path.join(dirName, filename); | ||
if (!watched_files[filepath]) { | ||
var inotify_options = { | ||
path: filepath, | ||
watch_for: inotify.IN_MODIFY, | ||
callback: function(e) { _server_inotify_hook(filepath) }, | ||
}; | ||
watched_files[filepath] = monitor.addWatch(inotify_options); | ||
util.log('dweb: watching ' + path.relative(argv.dir, filepath)); | ||
} | ||
} | ||
next(); | ||
}); | ||
walker.on('end', function() { | ||
setTimeout(_walk_directory, argv.poll); | ||
}); | ||
})(); // end function _walk_directory() | ||
// Start webserver serving out current directory and socketio server | ||
// for broadcasting reload events to connected clients. | ||
var app = connect() | ||
.use(intercept) | ||
.use(connect.static(process.cwd())) | ||
.listen(argv.port); | ||
// Start socket.io server | ||
.use(_intercept) | ||
.use(connect.static(argv.dir)) | ||
.listen(argv.port); | ||
var io = socketio.listen(app, {log: false}); | ||
util.log('dweb: webserver listening on port ' + argv.port); | ||
// Callback function for file modification | ||
var last_mtimes = {}; | ||
function _server_inotify_hook(filepath) { | ||
var stat = fs.statSync(filepath); | ||
var mtime = stat.mtime; | ||
if (last_mtimes[filepath] - mtime !== 0) { | ||
util.log('dweb: change detected in ' + filepath); | ||
io.sockets.emit('inotify', filepath); | ||
last_mtimes[filepath] = mtime; | ||
} | ||
} // end function _server_inotify_hook | ||
// Start inotify monitor | ||
var monitor = (new inotify()) | ||
.addWatch({ | ||
path: process.cwd(), | ||
watch_for: inotify.IN_MODIFY | inotify.IN_CREATE, | ||
callback: __inotify_hook_server | ||
}); | ||
util.log('Listening on port ' + argv.port); | ||
// Middleware to inject socket.io hook into HTML files | ||
function intercept(req, res, next) { | ||
var url = path.resolve('.' + (req.url == '/' ? '/index.html' : req.url)); | ||
// Middleware to inject socket.io hook into served HTML files | ||
function _intercept(req, res, next) { | ||
util.log(req.method + ' ' + req.url); | ||
if (url.length >= 4 && url.substr(url.length - 4) == 'html') { | ||
try { | ||
var html = inject_inotify_hook(fs.readFileSync(url).toString('utf8')); | ||
} catch(e) { | ||
if (typeof next == 'function') | ||
return next(); | ||
html = (e && e.toString()) || 'error'; | ||
} | ||
// TODO: replace with something that does not allow breaking out | ||
// of the server's root directory. | ||
var req_url = req.url == '/' ? '/index.html' : req.url; | ||
req_url = path.join(argv.dir, req_url); | ||
var extension = path.extname(req_url); | ||
if (extension !== '.html') return next(); | ||
// Inject socket.io listener into HTML file | ||
try { | ||
var html = _inject_inotify_hook(fs.readFileSync(req_url).toString('utf8')); | ||
res.writeHead(200, {'Content-Type': 'text/html'}); | ||
res.end(html); | ||
} else | ||
next(); | ||
} | ||
// Inotify event callback | ||
function __inotify_hook_server(event) { | ||
io.sockets.emit('inotify', event.name); | ||
} | ||
})(); | ||
// Inject the inotify hook HTML fragment into every HTML page served. | ||
// This does not attempt to parse the DOM in any way, and relies on the | ||
// existence of at least a </head> or </body> tag in the HTML file. | ||
function inject_inotify_hook(html) { | ||
var inject_pos = html.indexOf('</head>'); | ||
inject_pos = (inject_pos == -1) ? html.indexOf('</body>') : inject_pos; | ||
if (inject_pos != -1) | ||
html = [ html.substr(0, inject_pos), | ||
'<script src="http://localhost:' | ||
+ argv.port | ||
+ '/socket.io/socket.io.js"></script>', | ||
'<script language="javascript">', | ||
'window.addEventListener("load", __inotify_hook_client);', | ||
__inotify_hook_client.toString(), | ||
'</script>', | ||
html.substr(inject_pos) | ||
].join(''); | ||
return html; | ||
// Only parsed and called in the browser | ||
function __inotify_hook_client() { | ||
var files_to_watch = {}; | ||
// Watch all stylesheet files | ||
var parser = document.createElement('a'); | ||
for (var i = 0; i < document.styleSheets.length; i++) { | ||
parser.href = document.styleSheets[i].href; | ||
files_to_watch[parser.pathname.substr(1)] = true; | ||
res.end(html); | ||
} catch (e) { | ||
util.error('dweb: cannot inject listener into ' + req_url); | ||
return next(); | ||
} | ||
// Watch document root | ||
parser.href = document.location.href; | ||
var root = parser.pathname.substr(1); | ||
if (root == '') root = 'index.html'; | ||
files_to_watch[root] = true; | ||
} // end function _intercept() | ||
function _inject_inotify_hook(html) { | ||
var inject_pos = html.indexOf('</head>'); | ||
inject_pos = (inject_pos == -1) ? html.indexOf('</body>') : inject_pos; | ||
if (inject_pos == -1) throw 'could not find a </head> or </body> tag'; | ||
return [ html.substr(0, inject_pos) | ||
, util.format('<script src="http://localhost:%d/socket.io/socket.io.js"></script>\n', argv.port) | ||
, '<script language="javascript">' | ||
, _client_inotify_hook.toString() | ||
, 'window.addEventListener("load", _client_inotify_hook);' | ||
, '</script>' | ||
, html.substr(inject_pos) | ||
].join(''); | ||
} // end function _inject_inotify_hook() | ||
function _client_inotify_hook() { | ||
// Connect to SocketIO server | ||
var socket = io.connect(parser.protocol + '//' + parser.host); | ||
var url_parser = document.createElement('a'); | ||
url_parser.href = document.location.href; | ||
var socket = io.connect(url_parser.protocol + '//' + url_parser.host); | ||
socket.on('inotify', function (data) { | ||
if (data in files_to_watch); | ||
window.location.reload(); | ||
window.location.reload(); | ||
}); | ||
} | ||
} | ||
} // end function _client_inotify_hook() | ||
})(); // end function main() |
@@ -5,3 +5,3 @@ { | ||
"preferGlobal": true, | ||
"version": "0.1.3", | ||
"version": "0.1.4", | ||
"author": "Mayank Lahiri <mlahiri@gmail.com>", | ||
@@ -17,12 +17,16 @@ "description": "A debugging web server that auto-reloads pages on local modifications.", | ||
"keywords": [ | ||
"development", | ||
"debugging", | ||
"http", | ||
"server" | ||
"webserver", | ||
"instant refresh", | ||
"refresh on save" | ||
], | ||
"dependencies" : { | ||
"inotify": ">=1.2.1", | ||
"socket.io": ">=0.9.16", | ||
"connect": ">=2.9.0", | ||
"optimist": ">=0.6.0" | ||
"inotify": "1.2.x", | ||
"socket.io": "0.9.x", | ||
"connect": "2.9.x", | ||
"optimist": "0.6.x", | ||
"walk": "2.2.x" | ||
} | ||
} |
8322
118
5
5
+ Addedwalk@2.2.x
+ Addedactive-x-obfuscator@0.0.1(transitive)
+ Addedbase64id@0.1.0(transitive)
+ Addedbuffer-crc32@0.2.1(transitive)
+ Addedbytes@0.2.0(transitive)
+ Addedcommander@2.1.0(transitive)
+ Addedconnect@2.9.2(transitive)
+ Addedcookie@0.1.0(transitive)
+ Addedcookie-signature@1.0.1(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ AddedforEachAsync@2.2.1(transitive)
+ Addedfresh@0.2.0(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedinotify@1.2.1(transitive)
+ Addedisarray@0.0.1(transitive)
+ Addedmethods@0.0.1(transitive)
+ Addedmime@1.2.11(transitive)
+ Addedmultiparty@2.2.0(transitive)
+ Addednan@1.0.0(transitive)
+ Addednegotiator@0.2.8(transitive)
+ Addedoptions@0.0.6(transitive)
+ Addedpause@0.0.1(transitive)
+ Addedpolicyfile@0.0.4(transitive)
+ Addedqs@0.6.5(transitive)
+ Addedrange-parser@0.0.4(transitive)
+ Addedraw-body@0.0.3(transitive)
+ Addedreadable-stream@1.1.14(transitive)
+ Addedredis@0.7.3(transitive)
+ Addedsend@0.1.4(transitive)
+ Addedsequence@2.2.1(transitive)
+ Addedsocket.io@0.9.19(transitive)
+ Addedsocket.io-client@0.9.16(transitive)
+ Addedstream-counter@0.2.0(transitive)
+ Addedstring_decoder@0.10.31(transitive)
+ Addedtinycolor@0.0.1(transitive)
+ Addeduglify-js@1.2.5(transitive)
+ Addeduid2@0.0.2(transitive)
+ Addedwalk@2.2.1(transitive)
+ Addedws@0.4.32(transitive)
+ Addedxmlhttprequest@1.4.2(transitive)
+ Addedzeparser@0.0.5(transitive)
- Removed@socket.io/component-emitter@3.1.2(transitive)
- Removed@types/cookie@0.4.1(transitive)
- Removed@types/cors@2.8.17(transitive)
- Removed@types/node@20.14.2(transitive)
- Removedaccepts@1.3.8(transitive)
- Removedbase64id@2.0.0(transitive)
- Removedbindings@1.5.0(transitive)
- Removedconnect@3.7.0(transitive)
- Removedcookie@0.4.2(transitive)
- Removedcors@2.8.5(transitive)
- Removeddebug@2.6.9(transitive)
- Removedee-first@1.1.1(transitive)
- Removedencodeurl@1.0.2(transitive)
- Removedengine.io@6.5.4(transitive)
- Removedengine.io-parser@5.2.2(transitive)
- Removedescape-html@1.0.3(transitive)
- Removedfile-uri-to-path@1.0.0(transitive)
- Removedfinalhandler@1.1.2(transitive)
- Removedinotify@1.4.6(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removedms@2.0.0(transitive)
- Removednan@2.19.0(transitive)
- Removednegotiator@0.6.3(transitive)
- Removedobject-assign@4.1.1(transitive)
- Removedon-finished@2.3.0(transitive)
- Removedparseurl@1.3.3(transitive)
- Removedsocket.io@4.7.5(transitive)
- Removedsocket.io-adapter@2.5.4(transitive)
- Removedsocket.io-parser@4.2.4(transitive)
- Removedstatuses@1.5.0(transitive)
- Removedundici-types@5.26.5(transitive)
- Removedunpipe@1.0.0(transitive)
- Removedutils-merge@1.0.1(transitive)
- Removedvary@1.1.2(transitive)
- Removedws@8.11.0(transitive)
Updatedconnect@2.9.x
Updatedinotify@1.2.x
Updatedoptimist@0.6.x
Updatedsocket.io@0.9.x