Skip to content

Instantly share code, notes, and snippets.

@mklabs
Created January 16, 2012 17:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mklabs/1621995 to your computer and use it in GitHub Desktop.
Save mklabs/1621995 to your computer and use it in GitHub Desktop.
var fs = require('fs'),
path = require('path'),
url = require('url'),
socketio = require('socket.io'),
connect = require('connect'),
mime = connect.static.mime,
// one time-hit, get the file content of the socket.io client side script
ioScript = underscore.template(fs.readFileSync(path.join(__dirname, 'util/socket-enable.js'), 'utf8'));
//
// ### Tasks
//
// #### task:foo
task.registerBasicTask('foo', 'A basic tasks that emits event over socket.io', function(data, name) {
var socket = config('socket');
socket && socket.emit('changed');
});
// #### task:connect
//
// serve task, this one will use raw express (or just connect, should be enough)
// application with socket.io connection setup.
//
// It works by injecting a tiny client-side script on any `*.html` request (might be better
// if done on any `content-type: text/html`, this way it would be able to catch up requests,
// even those serving dynamic content, not just statics). That client side establish
// a new websocket connection and retrigger a page reload whenever the `changed` event is emitted.
//
//
// The server spawn is wrapped up in a grunt task, and set to the config a new `socket` object,
// when a new client side connection is done.
//
// task.registerTask('connect-watch', 'connect watch:reload');
//
// The `watch:reload` is setup to fire the `foo` tasks, whose role is to simply emit
// the `changed` event on the socket that might have been stored through `config('socket')`.
//
// todo: that tasks begins to get pretty long, might be worth moving the custom middleware, into
// a grunt helper or a simple function defined elsewhere (probably in its own module)
task.registerBasicTask('connect', 'Spawns up a local http server', function(data, name) {
// path
var dirname = path.resolve(name);
log.subhead('Spawning http server from dirname ' + dirname + ' on port: ' + data.port);
// setup the server
var server = connect();
// setup socketio
var io = socketio.listen(server);
io.enable('browser client minification');
io.enable('browser client etag');
io.enable('browser client gzip');
io.set('log level', 5);
// store the socket object in grunt's config, so that we can interract from other tasks
io.sockets.on('connection', config.bind(config, 'socket'));
// ignore favicon
server.use(connect.favicon());
// setup logs
data.logs && server.use(connect.logger(data.logs));
// custom static middleware, tricking the static one
server.use(function(req, res, next) {
// serve any static *.html, support of `index.html`
var parsed = url.parse(req.url),
// join / normalize from root dir
filepath = path.normalize(path.join(dirname, decodeURIComponent(parsed.pathname))),
// index.html support when trainling `/`
index = path.normalize('/') === filepath.charAt(filepath.length - 1),
ioResponse;
if(index) filepath += 'index.html';
// deal with our special socket.io client-side script
if(path.basename(filepath) === 'socket-enable.js') {
res.setHeader('Content-Type', mime.lookup('js'));
ioResponse = ioScript(data);
res.setHeader('Content-Length', ioResponse.length);
return res.end(ioResponse);
}
fs.stat(filepath, function(e, stat) {
// files do not exists, next with error only on unexpected errors
if(e) return next((e.code === 'ENOENT' || 'ENAMETOOLONG' === e.code) ? null : e);
// file is a dir, next to the directory listing if enabled
if(stat.isDirectory()) return next();
// anything that's not `*.html`, give back control to static / directory listing middleware
if(path.extname(filepath) !== '.html') return next();
// setup some basic headers, might add some. Below is pretty minimalist (might tweak and add
// basic caching stuff for example)
res.setHeader('Content-Type', mime.lookup(filepath));
// can't use the ideal stream / pipe case, we need to alter the html response
// by injecting that little socket.io client-side app.
fs.readFile(filepath, 'utf8', function(e, body) {
if(e) return next(e);
body = body.replace(/<\/body>/, function(w) {
return [
' <script defer src="/socket.io/socket.io.js"></script>',
' <script defer src="/socket-enable.js"></script>',
w
].join('\n');
});
res.setHeader('Content-Length', body.length);
res.end(body);
});
});
});
// static files
server.use(connect.static(dirname));
// directory serving
data.dirs && server.use(connect.directory(dirname));
// start the server
server.listen(data.port);
log.writeln('\033[90mserving \033[36m:path\033[90m on port \033[96m:port\033[0m'
.replace(':path', dirname)
.replace(':port', data.port)
);
});
config.init({
lint: {
files: ['js/*.js'],
build: ['grunt.js', 'build/tasks/*.js']
},
watch: {
files: '<config:lint.build>',
tasks: 'lint:build',
reload: {
files: '<config:lint.build>',
tasks: 'foo'
}
},
serve: {
intermediate: { port: 3000 },
publish: { port: 3001 }
},
connect: {
intermediate: {
port: 3000,
logs: 'dev',
dirs: true
},
publish: {
port: 3001,
logs: 'default',
dirs: true
}
},
foo: {
bar: []
}
});
task.registerTask('connect-watch', 'connect watch:reload');
(function() {
var url = 'http://localhost:<%= port %>',
socket = io.connect(url);
socket.on('changed', function(file, path, content) {
console.log('socket', arguments);
location.assign(location.pathname);
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment