Skip to content

Commit

Permalink
restart server and trigger LR on server file change
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Santero committed Oct 16, 2014
1 parent 8e6b9bc commit 22ee045
Show file tree
Hide file tree
Showing 23 changed files with 877 additions and 47 deletions.
62 changes: 62 additions & 0 deletions lib/models/server-watcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use strict';

var sane = require('sane');
var Task = require('./task');

module.exports = Task.extend({
verbose: true,

init: function() {
this.watcher = this.watcher || new sane.Watcher(this.watchedDir, {
verbose: this.verbose,
poll: this.polling()
});

this.watcher.on('change', this.didChange.bind(this));
this.watcher.on('add', this.didAdd.bind(this));
this.watcher.on('delete', this.didDelete.bind(this));
},

didChange: function (filepath) {
this.ui.writeLine('Server file changed: ' + filepath);

this.analytics.track({
name: 'server file change',
description: 'File changed: "' + filepath + '"'
});
},

didAdd: function (filepath) {
this.ui.writeLine('Server file added: ' + filepath);

this.analytics.track({
name: 'server file addition',
description: 'File added: "' + filepath + '"'
});
},

didDelete: function (filepath) {
this.ui.writeLine('Server file deleted: ' + filepath);

this.analytics.track({
name: 'server file deletion',
description: 'File deleted: "' + filepath + '"'
});
},

then: function() {
return this.watcher.then.apply(this.watcher, arguments);
},

on: function() {
this.watcher.on.apply(this.watcher, arguments);
},

off: function() {
this.watcher.off.apply(this.watcher, arguments);
},

polling: function () {
return this.options && this.options.watcher === 'polling';
}
});
20 changes: 18 additions & 2 deletions lib/tasks/serve.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
'use strict';

var fs = require('fs');
var path = require('path');
var LiveReloadServer = require('./server/livereload-server');
var ExpressServer = require('./server/express-server');
var Promise = require('../ext/promise');
var Task = require('../models/task');
var Watcher = require('../models/watcher');
var Builder = require('../models/builder');
var ServerWatcher = require('../models/server-watcher');

module.exports = Task.extend({
run: function(options) {
Expand All @@ -22,17 +25,30 @@ module.exports = Task.extend({
options: options
});

var serverRoot = './server';
var serverWatcher = null;
if (fs.existsSync(serverRoot)) {
serverWatcher = new ServerWatcher({
ui: this.ui,
analytics: this.analytics,
watchedDir: path.resolve(serverRoot)
});
}

var expressServer = new ExpressServer({
ui: this.ui,
project: this.project,
watcher: watcher
watcher: watcher,
serverRoot: serverRoot,
serverWatcher: serverWatcher
});

var liveReloadServer = new LiveReloadServer({
ui: this.ui,
analytics: this.analytics,
project: this.project,
watcher: watcher
watcher: watcher,
expressServer: expressServer
});

return Promise.all([
Expand Down
155 changes: 139 additions & 16 deletions lib/tasks/server/express-server.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
'use strict';

var Promise = require('../../ext/promise');
var Task = require('../../models/task');
var SilentError = require('../../errors/silent');
var path = require('path');
var EventEmitter = require('events').EventEmitter;
var chalk = require('chalk');
var Promise = require('../../ext/promise');
var Task = require('../../models/task');
var SilentError = require('../../errors/silent');

var cleanBaseURL = require('../../utilities/clean-base-url');

module.exports = Task.extend({
init: function() {
this.emitter = new EventEmitter();
this.express = this.express || require('express');
this.http = this.http || require('http');
this.serverRestartDelayTime = 100;
},

on: function() {
this.emitter.on.apply(this.emitter, arguments);
},

off: function() {
this.emitter.off.apply(this.emitter, arguments);
},

setupHttpServer: function() {
this.httpServer = this.http.createServer(this.app);

// We have to keep track of sockets so that we can close them
// when we need to restart.
this.sockets = {};
this.nextSocketId = 0;
this.httpServer.on('connection', function(socket) {
var socketId = this.nextSocketId++;
this.sockets[socketId] = socket;

socket.on('close', function() {
delete this.sockets[socketId];
}.bind(this));
}.bind(this));
},

listen: function(port, host) {
Expand All @@ -36,32 +64,127 @@ module.exports = Task.extend({
},

processAppMiddlewares: function(options) {
if (this.project.has('./server')) {
this.project.require('./server')(this.app, options);
if (this.project.has(this.serverRoot)) {
this.project.require(this.serverRoot)(this.app, options);
}
},

start: function(options) {
var ui = this.ui;
this.app = require('express')();
options.project = this.project;
options.watcher = this.watcher;
options.serverWatcher = this.serverWatcher;
options.ui = this.ui;

this.startOptions = options;

if (this.serverWatcher) {
this.serverWatcher.on('change', this.serverWatcherDidChange.bind(this));
this.serverWatcher.on('add', this.serverWatcherDidChange.bind(this));
this.serverWatcher.on('delete', this.serverWatcherDidChange.bind(this));
}

return this.startHttpServer()
.then(function () {
var baseURL = cleanBaseURL(options.baseURL);

options.ui.writeLine('Serving on http://' + options.host + ':' + options.port + baseURL);
});
},

serverWatcherDidChange: function() {
this.scheduleServerRestart();
},

scheduleServerRestart: function () {
// If multiple files change in quick succession, let's only restart the server once
if (!this.serverRestartScheduled) {
this.serverRestartScheduled = true;
var self = this;
setTimeout(function () {
self.serverRestartScheduled = false;
self.restartHttpServer();
}, this.serverRestartDelayTime);
}
},

restartHttpServer: function() {
var self = this;
if (!this.serverRestartPromise) {
this.serverRestartPromise =
this.stopHttpServer()
.then(function () {
self.invalidateCache(self.serverRoot);
return self.startHttpServer();
})
.then(function () {
self.emitter.emit('restart');
self.ui.writeLine('');
self.ui.writeLine(chalk.green('Server restarted.'));
self.ui.writeLine('');
})
.catch(function (err) {
self.ui.writeError(err);
})
.finally(function () {
self.serverRestartPromise = null;
});
return this.serverRestartPromise;
} else {
return this.serverRestartPromise.then(function () {
return self.restartHttpServer();
});
}
},

stopHttpServer: function() {
return new Promise(function (resolve, reject) {
if (!this.httpServer) {
return resolve();
}
this.httpServer.close(function (err) {
if (err) {
reject(err);
return;
}
this.httpServer = null;
resolve();
}.bind(this));

// We have to force close all sockets in order to get an fast restart
var sockets = this.sockets;
for (var socketId in sockets) {
sockets[socketId].destroy();
}
}.bind(this));
},

startHttpServer: function() {
this.app = this.express();
this.setupHttpServer();

options.project = this.project;
options.watcher = this.watcher;
options.ui = this.ui;
options.httpServer = this.httpServer;
var options = this.startOptions;
options.httpServer = this.httpServer;

this.processAppMiddlewares(options);
this.processAddonMiddlewares(options);

return this.listen(options.port, options.host)
.then(function() {
var baseURL = cleanBaseURL(options.baseURL);

ui.writeLine('Serving on http://' + options.host + ':' + options.port + baseURL);
})
.catch(function() {
throw new SilentError('Could not serve on http://' + options.host + ':' + options.port + '. It is either in use or you do not have permission.');
});
},

invalidateCache: function (serverRoot) {
var absoluteServerRoot = path.resolve(serverRoot);
if (absoluteServerRoot[absoluteServerRoot.length - 1] !== path.sep) {
absoluteServerRoot += path.sep;
}

var allKeys = Object.keys(require.cache);
for (var i = 0; i < allKeys.length; i++) {
if (allKeys[i].indexOf(absoluteServerRoot) === 0) {
delete require.cache[allKeys[i]];
}
}
}
});
16 changes: 16 additions & 0 deletions lib/tasks/server/livereload-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ module.exports = Task.extend({
// Reload on file changes
this.watcher.on('change', this.didChange.bind(this));

// Reload on express server restarts
this.expressServer.on('restart', this.didRestart.bind(this));

// Start LiveReload server
return this.listen(options.liveReloadPort)
.then(this.writeBanner.bind(this, options.liveReloadPort))
Expand Down Expand Up @@ -76,5 +79,18 @@ module.exports = Task.extend({
message: 'live-reload'
});
}
},

didRestart: function() {
this.liveReloadServer().changed({
body: {
files: ['LiveReload files']
}
});

this.analytics.track({
name: 'express server',
message: 'live-reload'
});
}
});
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"resolve",
"rimraf",
"rsvp",
"sane",
"semver",
"testem",
"through",
Expand Down Expand Up @@ -132,6 +133,7 @@
"resolve": "^1.0.0",
"rimraf": "^2.2.8",
"rsvp": "^3.0.14",
"sane": "0.8.0",
"semver": "^4.0.3",
"symlink-or-copy": "^1.0.0",
"temp": "0.8.1",
Expand Down
Loading

0 comments on commit 22ee045

Please sign in to comment.