Skip to content

Commit

Permalink
Fastboot serve refactor (unifying the experience with ember-cli)
Browse files Browse the repository at this point in the history
  • Loading branch information
kratiahuja committed Apr 7, 2017
1 parent e71e664 commit ecdb661
Show file tree
Hide file tree
Showing 18 changed files with 861 additions and 308 deletions.
4 changes: 3 additions & 1 deletion README.md
Expand Up @@ -33,6 +33,8 @@ ember install ember-cli-fastboot
* `ember fastboot --serve-assets`
* Visit your app at `http://localhost:3000`.

**Note**: If your app is running ember-cli v2.12.0-beta.1+, you can just use `ember serve` instead of `ember fastboot --serve-assets`.

You may be shocked to learn that minified code runs faster in Node than
non-minified code, so you will probably want to run the production
environment build for anything "serious."
Expand Down Expand Up @@ -381,7 +383,7 @@ present.

### Prototype extensions

Prototype extensions do not currently work across node "realms." Fastboot
Prototype extensions do not currently work across node "realms." Fastboot
applications operate in two realms, a normal node environment and a [virtual machine](https://nodejs.org/api/vm.html). Passing objects that originated from the normal realm will not contain the extension methods
inside of the sandbox environment. For this reason, it's encouraged to [disable prototype extensions](https://guides.emberjs.com/v2.4.0/configuring-ember/disabling-prototype-extensions/).

Expand Down
59 changes: 58 additions & 1 deletion index.js
Expand Up @@ -6,6 +6,9 @@ var path = require('path');
var EventEmitter = require('events').EventEmitter;
var mergeTrees = require('broccoli-merge-trees');
var VersionChecker = require('ember-cli-version-checker');
var FastBootExpressMiddleware = require('fastboot-express-middleware');
var FastBoot = require('fastboot');
var chalk = require('chalk');

var patchEmberApp = require('./lib/ext/patch-ember-app');
var fastbootAppModule = require('./lib/utilities/fastboot-app-module');
Expand Down Expand Up @@ -139,12 +142,66 @@ module.exports = {
return fastbootBuild.toTree();
},

serverMiddleware: function(options) {
var emberCliVersion = this._getEmberCliVersion();
var app = options.app;
var options = options.options;

if (emberCliVersion.satisfies('>= 2.12.0-beta.1')) {
// only run the middleware when ember-cli version for app is above 2.12.0-beta.1 since
// that version contains API to hook fastboot into ember-cli

app.use((req, resp, next) => {
var broccoliHeader = req.headers['x-broccoli'];
var outputPath = broccoliHeader['outputPath'];

if (broccoliHeader['url'] === req.serveUrl) {
// if it is a base page request, then have fastboot serve the base page
// TODO(future): provide a way to turn this off without needing to uninstall this addon
if (!this.fastboot) {
// TODO(future): make this configurable for allowing apps to pass sandboxGlobals
// and custom sandbox class
this.ui.writeLine(chalk.green('App is being served by FastBoot'));
this.fastboot = new FastBoot({
distPath: outputPath
});
}

var fastbootMiddleware = FastBootExpressMiddleware({
fastboot: this.fastboot
});

fastbootMiddleware(req, resp, next);
} else {
// forward the request to the next middleware (example other assets, proxy etc)
next();
}
})
}
},

outputReady: function() {
this.emit('outputReady');
},

postBuild: function() {
postBuild: function(result) {
this.emit('postBuild');
if (this.fastboot) {
// should we reload fastboot if there are only css changes? Seems it maynot be needed.
// TODO(future): we can do a smarter reload here by running fs-tree-diff on files loaded
// in sandbox.
this.ui.writeLine(chalk.blue('Reloading FastBoot...'));
this.fastboot.reload({
distPath: result.directory
});
}
},

_getEmberCliVersion: function() {
var VersionChecker = require('ember-cli-version-checker');
var checker = new VersionChecker(this);

return checker.for('ember-cli', 'npm');
},

_getEmberVersion: function() {
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/fastboot-build.js
Expand Up @@ -20,7 +20,7 @@ module.exports = {
project: this.project
});

deprecate("Use of ember fastboot:build is deprecated. Please use ember build instead.");
deprecate('Use of ember fastboot:build is deprecated. Please use ember build instead.');

return buildTask.run(options);
},
Expand Down
14 changes: 14 additions & 0 deletions lib/commands/fastboot.js
Expand Up @@ -4,6 +4,7 @@ const RSVP = require('rsvp');
const getPort = RSVP.denodeify(require('portfinder').getPort);
const ServerTask = require('../tasks/fastboot-server');
const SilentError = require('silent-error');
const VersionChecker = require('ember-cli-version-checker');

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

Expand All @@ -28,12 +29,14 @@ module.exports = function(addon) {
ServerTask,

run(options) {
const printDeprecations = () => this.printDeprecations(options);
const runBuild = () => this.runBuild(options);
const runServer = () => this.runServer(options);
const startServer = (serverTask) => this.startServer(serverTask, options);
const blockForever = this.blockForever;

return this.checkPort(options)
.then(printDeprecations)
.then(runServer) // starts on postBuild SIGHUP
.then(options.build ? runBuild : startServer)
.then(blockForever);
Expand Down Expand Up @@ -74,5 +77,16 @@ module.exports = function(addon) {
});
},

printDeprecations(options) {
var checker = new VersionChecker(this);
var dep = checker.for('ember-cli', 'npm');

if (dep.satisfies('>= 2.12.0-beta.1')) {
this.ui.writeDeprecateLine('`ember fastboot --serve-assets` is deprecated. Please use `ember serve` to serve your fastboot assets.');
} else {
this.ui.writeWarnLine('`ember fastboot` will no longer work after FastBoot 1.0 is released.');
}
},

}
};
6 changes: 5 additions & 1 deletion package.json
Expand Up @@ -23,12 +23,13 @@
"broccoli-merge-trees": "^1.1.1",
"broccoli-plugin": "^1.2.1",
"broccoli-stew": "^1.2.0",
"chalk": "^1.1.3",
"compression": "^1.6.2",
"core-object": "^2.0.5",
"debug": "^2.2.0",
"ember-cli-babel": "^5.1.7",
"ember-cli-eslint": "^3.0.2",
"ember-cli-version-checker": "^1.1.6",
"ember-cli-version-checker": "^1.2.0",
"express": "^4.8.5",
"fastboot-express-middleware": "1.0.0-rc.7",
"fastboot-filter-initializers": "0.0.2",
Expand Down Expand Up @@ -77,6 +78,9 @@
"configPath": "tests/dummy/config",
"after": [
"broccoli-asset-rev"
],
"before": [
"broccoli-serve-files"
]
}
}
72 changes: 53 additions & 19 deletions test/async-content-test.js
Expand Up @@ -8,31 +8,65 @@ var get = RSVP.denodeify(request);
describe('async content via deferred content', function() {
this.timeout(400000);

var app;
describe('with fastboot command', function() {
var app;

before(function() {
before(function() {

app = new AddonTestApp();
app = new AddonTestApp();

return app.create('async-content')
.then(function() {
return app.startServer({
command: 'fastboot',
additionalArguments: ['--serve-assets']
return app.create('async-content')
.then(function() {
return app.startServer({
command: 'fastboot',
additionalArguments: ['--serve-assets']
});
});
});
});
});

after(function() {
return app.stopServer();
});

after(function() {
return app.stopServer();
it('waits for async content when using `fastboot.deferRendering`', function() {
return get({
url: 'http://localhost:49741/'
})
.then(function(response) {
expect(response.body).to.contain('Async content: foo');
});
});
});

it('waits for async content when using `fastboot.deferRendering`', function() {
return get({
url: 'http://localhost:49741/'
})
.then(function(response) {
expect(response.body).to.contain('Async content: foo');
});
describe('with serve command', function() {
var app;

before(function() {

app = new AddonTestApp();

return app.create('async-content')
.then(function() {
return app.startServer({
command: 'serve'
});
});
});

after(function() {
return app.stopServer();
});

it('waits for async content when using `fastboot.deferRendering`', function() {
return get({
url: 'http://localhost:49741/',
headers: {
'Accept': 'text/html'
}
})
.then(function(response) {
expect(response.body).to.contain('Async content: foo');
});
});
});
});
85 changes: 61 additions & 24 deletions test/custom-html-file-test.js
@@ -1,38 +1,75 @@
var expect = require('chai').expect;
var RSVP = require('rsvp');
var request = RSVP.denodeify(require('request'));
var expect = require('chai').expect;
var RSVP = require('rsvp');
var request = RSVP.denodeify(require('request'));

var AddonTestApp = require('ember-cli-addon-tests').AddonTestApp;
var AddonTestApp = require('ember-cli-addon-tests').AddonTestApp;

describe('custom htmlFile', function() {
this.timeout(400000);

var app;
describe('with fastboot command', function() {
var app;

before(function() {
app = new AddonTestApp();
before(function() {
app = new AddonTestApp();

return app.create('custom-html-file')
.then(function() {
return app.startServer({
command: 'fastboot',
additionalArguments: ['--serve-assets']
return app.create('custom-html-file')
.then(function() {
return app.startServer({
command: 'fastboot',
additionalArguments: ['--serve-assets']
});
});
});
});
});

after(function() {
return app.stopServer();
});

it('uses custom htmlFile', function() {
return request('http://localhost:49741/')
.then(function(response) {
expect(response.statusCode).to.equal(200);
expect(response.headers["content-type"]).to.eq("text/html; charset=utf-8");

after(function() {
return app.stopServer();
expect(response.body).to.contain("<title>custom index</title>");
expect(response.body).to.contain("<h1>application template</h1>");
});
});
});

it('uses custom htmlFile', function() {
return request('http://localhost:49741/')
.then(function(response) {
expect(response.statusCode).to.equal(200);
expect(response.headers["content-type"]).to.eq("text/html; charset=utf-8");
describe('with serve command', function() {
var app;

before(function() {
app = new AddonTestApp();

expect(response.body).to.contain("<title>custom index</title>");
expect(response.body).to.contain("<h1>application template</h1>");
});
return app.create('custom-html-file')
.then(function() {
return app.startServer({
command: 'serve'
});
});
});

after(function() {
return app.stopServer();
});

it('uses custom htmlFile', function() {
return request({
url: 'http://localhost:49741/',
headers: {
'Accept': 'text/html'
}
})
.then(function(response) {
expect(response.statusCode).to.equal(200);
expect(response.headers["content-type"]).to.eq("text/html; charset=utf-8");

expect(response.body).to.contain("<title>custom index</title>");
expect(response.body).to.contain("<h1>application template</h1>");
});
});
});
});

0 comments on commit ecdb661

Please sign in to comment.