Skip to content

Commit

Permalink
Merge pull request #200 from pwfisher/master
Browse files Browse the repository at this point in the history
ember fastboot --watch
  • Loading branch information
danmcclain committed Jun 17, 2016
2 parents 664fe11 + 276793c commit 1635d68
Show file tree
Hide file tree
Showing 6 changed files with 516 additions and 66 deletions.
6 changes: 5 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ module.exports = {
});

return fastbootBuild.toTree();
}
},

postBuild: function() {
process.emit('SIGHUP');
},

};
107 changes: 44 additions & 63 deletions lib/commands/fastboot.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
var RSVP = require('rsvp');
var exec = RSVP.denodeify(require('child_process').exec);
'use strict';

const RSVP = require('rsvp');
const getPort = RSVP.denodeify(require('portfinder').getPort);
const ServerTask = require('../tasks/fastboot-server');
const SilentError = require('silent-error');

const blockForever = () => (new RSVP.Promise(() => {}));
const noop = function() { };

module.exports = {
name: 'fastboot',
description: 'Runs a server to render your app using FastBoot.',
description: 'Builds and serves your FastBoot app, rebuilding on file changes.',

availableOptions: [
{ name: 'build', type: Boolean, default: true },
{ name: 'watch', type: Boolean, default: true, aliases: ['w'] },
{ name: 'environment', type: String, default: 'development', aliases: ['e',{'dev' : 'development'}, {'prod' : 'production'}] },
{ name: 'serve-assets', type: Boolean, default: false },
{ name: 'host', type: String, default: '::' },
Expand All @@ -15,75 +23,48 @@ module.exports = {
{ name: 'assets-path', type: String, default: 'dist' }
],

runCommand: function(appName, options) {
var commandOptions = this.commandOptions;
var outputPath = commandOptions.outputPath;
var assetsPath = commandOptions.assetsPath;
var ui = this.ui;

ui.writeLine("Installing FastBoot npm dependencies");

return exec('npm install', { cwd: outputPath })
.then(function() {
var fastbootMiddleware = require('fastboot-express-middleware');
var RSVP = require('rsvp');
var express = require('express');

var middleware = fastbootMiddleware(outputPath);

var app = express();

if (commandOptions.serveAssets) {
app.get('/', middleware);
app.use(express.static(assetsPath));
}

app.get('/*', middleware);

app.use(function(req, res) {
res.sendStatus(404);
});

var listener = app.listen(options.port, options.host, function() {
var host = listener.address().address;
var port = listener.address().port;
var family = listener.address().family;
blockForever,
getPort,
ServerTask,

if (family === 'IPv6') { host = '[' + host + ']'; }
run(options) {
const runBuild = () => this.runBuild(options);
const runServer = () => this.runServer(options);
const blockForever = this.blockForever;

ui.writeLine('Ember FastBoot running at http://' + host + ":" + port);
});
return this.checkPort(options)
.then(runServer) // starts on postBuild SIGHUP
.then(options.build ? runBuild : noop)
.then(blockForever);
},

// Block forever
return new RSVP.Promise(function() { });
});
runServer(options) {
const ServerTask = this.ServerTask;
const serverTask = new ServerTask({
ui: this.ui,
});
return serverTask.run(options);
},

triggerBuild: function(commandOptions) {
var BuildTask = this.tasks.Build;
var buildTask = new BuildTask({
runBuild(options) {
const BuildTask = options.watch ? this.tasks.BuildWatch : this.tasks.Build;
const buildTask = new BuildTask({
ui: this.ui,
analytics: this.analytics,
project: this.project
project: this.project,
});

return buildTask.run(commandOptions);
buildTask.run(options); // no return, BuildWatch.run blocks forever
},

run: function(options) {
this.commandOptions = options;

var runCommand = function() {
var appName = process.env.EMBER_CLI_FASTBOOT_APP_NAME || this.project.name();

return this.runCommand(appName, options);
}.bind(this);

if (options.build) {
return this.triggerBuild(options)
.then(runCommand);
}
checkPort(options) {
return this.getPort({ port: options.port, host: options.host })
.then((foundPort) => {
if (options.port !== foundPort && options.port !== 0) {
const message = `Port ${options.port} is already in use.`;
return Promise.reject(new SilentError(message));
}
options.port = foundPort;
});
},

return runCommand();
}
};
121 changes: 121 additions & 0 deletions lib/tasks/fastboot-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use strict';

const RSVP = require('rsvp');
const CoreObject = require('core-object');
const debug = require('debug')('ember-cli-fastboot/server');
const exec = RSVP.denodeify(require('child_process').exec);
const http = require('http');
const path = require('path');

module.exports = CoreObject.extend({

exec,
http,
httpServer: null,
nextSocketId: 0,
require,
restartAgain: false,
restartPromise: null,
sockets: {},

run(options) {
debug('run');
const restart = () => this.restart(options);
process.on('SIGHUP', restart);
},

start(options) {
debug('start');
this.ui.writeLine('Installing FastBoot npm dependencies');

return this.exec('npm install', { cwd: options.outputPath })
.then(() => {
const middleware = this.require('fastboot-express-middleware')(options.outputPath);
const express = this.require('express');
const app = express();

if (options.serveAssets) {
app.get('/', middleware);
app.use(express.static(options.assetsPath));
}
app.get('/*', middleware);
app.use((req, res) => res.sendStatus(404));

this.httpServer = this.http.createServer(app);

// Track open sockets for fast restart
this.httpServer.on('connection', (socket) => {
const socketId = this.nextSocketId++;
debug(`open socket ${socketId}`);
this.sockets[socketId] = socket;
socket.on('close', () => {
debug(`close socket ${socketId}`);
delete this.sockets[socketId];
});
});

this.httpServer.listen(options.port, options.host, () => {
const o = this.httpServer.address();
const port = o.port;
const family = o.family;
let host = o.address;
if (family === 'IPv6') { host = `[${host}]`; }
this.ui.writeLine(`Ember FastBoot running at http://${host}:${port}`);
});
});
},

stop() {
debug('stop');
return new RSVP.Promise((resolve, reject) => {
if (!this.httpServer) { return resolve(); }

// Stop accepting new connections
this.httpServer.close((err) => {
debug('close', Object.keys(this.sockets));
if (err) { return reject(err); }
this.httpServer = null;
resolve();
});

// Force close existing connections
Object.keys(this.sockets).forEach(k => this.sockets[k].destroy());
});
},

restart(options) {
if (this.restartPromise) {
debug('schedule immediate restart');
this.restartAgain = true;
return;
}
debug('restart');
this.restartPromise = this.stop()
.then(() => this.clearRequireCache(options.outputPath))
.then(() => this.start(options))
.catch(e => this.ui.writeLine(e))
.finally(() => {
this.restartPromise = null;
if (this.restartAgain) {
debug('restart again')
this.restartAgain = false;
this.restart(options);
}
});
return this.restartPromise;
},

clearRequireCache: function (serverRoot) {
debug('clearRequireCache');
let absoluteServerRoot = path.resolve(serverRoot);
if (absoluteServerRoot.slice(-1) !== path.sep) {
absoluteServerRoot += path.sep;
}
Object.keys(require.cache).forEach(function (key) {
if (key.indexOf(absoluteServerRoot) === 0) {
delete require.cache[key];
}
});
},

});
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"test:precook": "node node_modules/ember-cli-addon-tests/scripts/precook-node-modules.js"
},
"engines": {
"node": ">= 0.10.0"
"node": ">= 4.0.0"
},
"author": "Tom Dale & Yehuda Katz <tomhuda@tilde.io>",
"license": "MIT",
Expand All @@ -23,7 +23,9 @@
"chai": "^2.2.0",
"chai-fs": "peterkc/chai-fs#31deec5232297e2887a2ffb39b9081364c8e78e1",
"chalk": "^1.1.1",
"core-object": "2.0.1",
"cpr": "^1.1.1",
"debug": "^2.2.0",
"ember-cli": "^2.5.0",
"ember-cli-addon-tests": "^0.3.0",
"ember-cli-app-version": "0.5.0",
Expand All @@ -35,6 +37,7 @@
"ember-cli-inject-live-reload": "^1.3.1",
"ember-cli-qunit": "^1.0.0",
"ember-cli-release": "^1.0.0-beta.1",
"ember-cli-string-utils": "^1.0.0",
"ember-cli-uglify": "^1.2.0",
"ember-data": "1.13.8",
"ember-export-application-global": "^1.0.3",
Expand All @@ -47,6 +50,7 @@
"loader.js": "^4.0.1",
"mocha": "^2.2.4",
"request": "^2.55.0",
"sinon": "^1.17.4",
"symlink-or-copy": "^1.0.1",
"temp": "^0.8.3",
"walk-sync": "^0.2.5"
Expand All @@ -72,6 +76,8 @@
"fastboot-filter-initializers": "0.0.2",
"lodash.defaults": "^4.0.1",
"lodash.uniq": "^4.2.0",
"rsvp": "^3.0.16"
"portfinder": "^1.0.3",
"rsvp": "^3.0.16",
"silent-error": "^1.0.0"
}
}
Loading

0 comments on commit 1635d68

Please sign in to comment.