diff --git a/bin/ember-fastboot b/bin/ember-fastboot index 9e88d1d..bf0d870 100755 --- a/bin/ember-fastboot +++ b/bin/ember-fastboot @@ -43,11 +43,22 @@ var server = new FastBootServer({ ui: ui }); -var app = express(); +console.log('Booting Ember app...'); -app.get('/*', server.middleware()); +// FastFail™: this is not mandatory; the first call to visit would +// also boot the app anyway. This is just to provide useful feedback +// instead of booting a server that keeps serving 500. +// +// Note that Application#boot is still a private API atm, so it might +// go through more churn in the near term. +server.app.boot().then(function() { + console.log('Ember app booted successfully.'); -var listener = app.listen(options.port, function() { + var app = express(); + + app.get('/*', server.middleware()); + + var listener = app.listen(options.port, function() { var host = listener.address().address; var port = listener.address().port; var family = listener.address().family; @@ -55,4 +66,15 @@ var listener = app.listen(options.port, function() { if (family === 'IPv6') { host = '[' + host + ']'; } console.log('Ember FastBoot running at http://' + host + ":" + port); +}, function(error) { + if (error.stack) { + console.error('An error occured when booting Ember app...'); + console.error(error.stack); + } else if (error.message) { + console.error('An error occured when booting Ember app: ' + error.message); + } else { + console.error('An unknown error occured when booting Ember app: ' + JSON.stringify(error)); + } + + process.exit(1); }); diff --git a/lib/ember-app.js b/lib/ember-app.js index f691b03..075d7e2 100644 --- a/lib/ember-app.js +++ b/lib/ember-app.js @@ -8,23 +8,20 @@ var najax = require('najax'); var debug = require('debug')('ember-cli-fastboot:ember-app'); var emberDebug = require('debug')('ember-cli-fastboot:ember'); -function EmberApp(options) { - this.appFile = options.appFile; - this.vendorFile = options.vendorFile; +var HTMLSerializer = new SimpleDOM.HTMLSerializer(SimpleDOM.voidMap); - debug("app created; app=%s; vendor=%s", this.appFile, this.vendorFile); +function EmberApp(options) { + var appFilePath = options.appFile; + var vendorFilePath = options.vendorFile; - // Promise that represents the completion of app boot. - var appBoot = RSVP.defer(); + debug("app created; app=%s; vendor=%s", appFilePath, vendorFilePath); // Create the sandbox, giving it the resolver to resolve once the app // has booted. - var sandbox = createSandbox(appBoot.resolve, { - najax: najax - }); + var sandbox = createSandbox({ najax: najax }); - appFile = fs.readFileSync(this.appFile, 'utf8'); - vendorFile = fs.readFileSync(this.vendorFile, 'utf8'); + var appFile = fs.readFileSync(appFilePath, 'utf8'); + var vendorFile = fs.readFileSync(vendorFilePath, 'utf8'); sandbox.run(vendorFile); debug("vendor file evaluated"); @@ -32,16 +29,38 @@ function EmberApp(options) { sandbox.run(appFile); debug("app file evaluated"); - this.waitForBoot = function() { - return appBoot.promise; - }; + var AppFactory = sandbox.require('~fastboot/app-factory'); + + if (!AppFactory || typeof AppFactory['default'] !== 'function') { + throw new Error('Failed to load Ember app from ' + appFilePath + ', make sure it was built for FastBoot with the `ember fastboot:build` command.'); + } + + this._app = AppFactory['default'](); +} - this.waitForBoot().then(function() { - debug("app booted"); +EmberApp.prototype.boot = function() { + return this._app.boot(); +}; + +EmberApp.prototype.visit = function(url) { + var doc = new SimpleDOM.Document(); + var rootElement = doc.body; + var options = { isBrowser: false, document: doc, rootElement: rootElement }; + + return this._app.visit(url, options).then(function(instance) { + try { + return { + url: instance.getURL(), // TODO: use this to determine whether to 200 or redirect + title: doc.title, + body: HTMLSerializer.serialize(rootElement) // This matches the current code; but we probably want `serializeChildren` here + }; + } finally { + instance.destroy(); + } }); } -function createSandbox(appBootResolver, dependencies) { +function createSandbox(dependencies) { var wrappedConsole = Object.create(console); wrappedConsole.error = function() { console.error.apply(console, Array.prototype.map.call(arguments, function(a) { @@ -50,11 +69,6 @@ function createSandbox(appBootResolver, dependencies) { }; var sandbox = { - // Expose this so that the FastBoot initializer has access to the fake DOM. - // We don't expose this as `document` so that other libraries don't mistakenly - // think they have a full DOM. - SimpleDOM: SimpleDOM, - // Expose the console to the FastBoot environment so we can debug console: wrappedConsole, @@ -64,12 +78,6 @@ function createSandbox(appBootResolver, dependencies) { // Convince jQuery not to assume it's in a browser module: { exports: {} }, - // Expose a hook for the Ember app to provide its handleURL functionality - FastBoot: { - resolve: appBootResolver, - debug: emberDebug - }, - URL: require("url") }; diff --git a/lib/server.js b/lib/server.js index a540329..5c76e19 100644 --- a/lib/server.js +++ b/lib/server.js @@ -59,15 +59,13 @@ FastBootServer.prototype.middleware = function() { var server = this; - this.app.waitForBoot().then(function(handleURL) { - debug("handling url; url=%s", path); - handleURL(path).then( - success, failure - ).finally(function() { + debug("handling url; url=%s", path); + + this.app.visit(path) + .then(success, failure) + .finally(function() { debug("finished handling; url=%s", path); - }); - }) - .catch(failure); + }) function success(result) { server.handleSuccess(res, path, result);