Make boot asynchronous

This commit is contained in:
Raymond Feng 2016-05-04 15:57:56 -07:00 committed by David Cheung
parent 3443c8ba1b
commit b0df4207c4
11 changed files with 1987 additions and 1380 deletions

View File

@ -21,7 +21,7 @@ var Bootstrapper = require('./lib/bootstrapper').Bootstrapper;
* @header boot(app)
*/
exports = module.exports = function bootBrowserApp(app, options) {
exports = module.exports = function bootBrowserApp(app, options, callback) {
// Only using options.id to identify the browserified bundle to load for
// this application. If no Id was provided, load the default bundle.
var moduleName = 'loopback-boot#instructions';
@ -39,6 +39,6 @@ exports = module.exports = function bootBrowserApp(app, options) {
app: app,
instructions: instructions,
};
bootstrapper.run(context);
return bootstrapper.run(context, callback);
};

View File

@ -146,6 +146,11 @@ var utils = require('./lib/utils');
*/
exports = module.exports = function bootLoopBackApp(app, options, callback) {
if (typeof options === 'function' && callback === undefined) {
callback = options;
options = {};
}
options = options || {};
// backwards compatibility with loopback's app.boot
options.env = options.env || app.get('env');
@ -156,17 +161,14 @@ exports = module.exports = function bootLoopBackApp(app, options, callback) {
app: app,
};
bootstrapper.run(context, callback);
return bootstrapper.run(context, callback);
};
exports.compile = function(options) {
exports.compile = function(options, done) {
var bootstrapper = new Bootstrapper(options);
bootstrapper.phases = ['load', 'compile'];
var context = {};
bootstrapper.run(context, function(err) {
if (err) throw err;
});
return context.instructions;
return bootstrapper.run(context, done);
};
/**
@ -179,9 +181,13 @@ exports.compile = function(options) {
*
* @header boot.compileToBrowserify(options, bundler)
*/
exports.compileToBrowserify = function(options, bundler) {
var instructions = exports.compile(options);
addInstructionsToBrowserify({ instructions: instructions }, bundler);
exports.compileToBrowserify = function(options, bundler, done) {
return exports.compile(options, function(err, context) {
if (err) return done(err);
addInstructionsToBrowserify({ instructions: context.instructions },
bundler);
done();
});
};
/* -- undocumented low-level API -- */
@ -202,9 +208,5 @@ exports.execute = function(app, instructions, done) {
app: app,
instructions: instructions,
};
bootstrapper.run(context, function(err) {
if (err) throw err;
if (done) done(err);
});
return context;
return bootstrapper.run(context, done);
};

34
lib/bootstrapper.js vendored
View File

@ -144,42 +144,40 @@ Bootstrapper.prototype.run = function(context, done) {
var phases = context.phases || this.phases;
var bootPlugins = this.getExtensions('/boot');
async.eachSeries(phases, function(phase, done) {
async.eachSeries(phases, function(phase, cb1) {
debug('Phase %s', phase);
async.eachSeries(bootPlugins, function(plugin, done) {
async.eachSeries(bootPlugins, function(plugin, cb2) {
var result;
if (typeof plugin.handler[phase] === 'function') {
debug('Invoking %s.%s', plugin.handler.name, phase);
try {
if (plugin.handler[phase].length === 2) {
plugin.handler[phase](context, done);
plugin.handler[phase](context, cb2);
} else {
result = plugin.handler[phase](context);
if (typeof Promise !== 'undefined') {
if (result && typeof result.then === 'function') {
result.then(function(value) {
done(null, value);
}).catch(function(err) {
debug(err);
done(err);
});
} else {
done(null, result);
}
Promise.resolve(result).then(function(value) {
cb2(null, value);
}).catch(function(err) {
debug(err);
cb2(err);
});
} else {
done(null, result);
cb2(null, result);
}
}
} catch (err) {
debug(err);
done(err);
cb2(err);
}
} else {
debug('Skipping %s.%s', plugin.handler.name, phase);
return done();
return cb2();
}
}, done);
}, done);
}, cb1);
}, function(err) {
return done(err, context);
});
return done.promise;
};

View File

@ -77,9 +77,14 @@ function runScripts(app, list, callback) {
});
} else {
debug('Starting sync function %s', f.path);
f.func(app);
debug('Sync function finished %s', f.path);
done();
var error;
try {
f.func(app);
debug('Sync function finished %s', f.path);
} catch (err) {
error = err;
}
done(error);
}
}, callback);
}

View File

@ -4,6 +4,7 @@
// License text available at https://opensource.org/licenses/MIT
var boot = require('../');
var async = require('async');
var exportBrowserifyToFile = require('./helpers/browserify').exportToSandbox;
var fs = require('fs');
var path = require('path');
@ -40,20 +41,21 @@ describe('browser support for multiple apps', function() {
browserifyTestApps(apps, function(err, bundlePath) {
if (err) return done(err);
var bundledApps = executeBundledApps(bundlePath, apps);
var app1 = bundledApps.defaultApp;
var app2 = bundledApps.browserApp2;
var bundledApps = executeBundledApps(bundlePath, apps, function(err) {
var app1 = bundledApps.defaultApp;
var app2 = bundledApps.browserApp2;
expect(app1.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app1.models)).to.include('Customer');
expect(Object.keys(app1.models)).to.not.include('Robot');
expect(app1.models.Customer.settings).to.have.property('_customized',
'Customer');
expect(app1.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app1.models)).to.include('Customer');
expect(Object.keys(app1.models)).to.not.include('Robot');
expect(app1.models.Customer.settings).to.have.property('_customized',
'Customer');
expect(Object.keys(app2.models)).to.include('Robot');
expect(Object.keys(app2.models)).to.not.include('Customer');
expect(Object.keys(app2.models)).to.include('Robot');
expect(Object.keys(app2.models)).to.not.include('Customer');
done();
done();
});
});
});
});
@ -74,6 +76,7 @@ function browserifyTestApps(apps, next) {
addPlugins(b);
var bundles = [];
for (var i in apps) {
var appDir = apps[i].appDir;
var appFile = apps[i].appFile;
@ -90,28 +93,37 @@ function browserifyTestApps(apps, next) {
appRootDir: appDir,
};
}
boot.compileToBrowserify(opts, b);
bundles.push(opts);
}
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
async.eachSeries(bundles, function(opts, done) {
boot.compileToBrowserify(opts, b, done);
}, function(err) {
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
});
}
function executeBundledApps(bundlePath, apps) {
function executeBundledApps(bundlePath, apps, done) {
var code = fs.readFileSync(bundlePath);
var context = createBrowserLikeContext();
vm.runInContext(code, context, bundlePath);
var ids = [];
var script = 'var apps = {};\n';
for (var i in apps) {
var moduleName = apps[i].moduleName;
var id = apps[i].appId || 'defaultApp';
ids.push(id);
script += 'apps.' + id + ' = require("' + moduleName + '");\n';
}
script += 'apps;\n';
var appsInContext = vm.runInContext(script, context);
printContextLogs(context);
async.each(ids, function(id, done) {
appsInContext[id].start(done);
}, function(err) {
printContextLogs(context);
done();
});
return appsInContext;
}

View File

@ -58,19 +58,18 @@ describe('browser support', function() {
browserifyTestApp(appDir, function(err, bundlePath) {
if (err) return done(err);
var app = executeBundledApp(bundlePath);
var app = executeBundledApp(bundlePath, function(err) {
// configured in fixtures/browser-app/boot/configure.js
expect(app.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app.models)).to.include('Customer');
expect(app.models.Customer.settings)
.to.have.property('_customized', 'Customer');
// configured in fixtures/browser-app/boot/configure.js
expect(app.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app.models)).to.include('Customer');
expect(app.models.Customer.settings)
.to.have.property('_customized', 'Customer');
// configured in fixtures/browser-app/component-config.json
// and fixtures/browser-app/components/dummy-component.js
expect(app.dummyComponentOptions).to.eql({ option: 'value' });
done();
// configured in fixtures/browser-app/component-config.json
// and fixtures/browser-app/components/dummy-component.js
expect(app.dummyComponentOptions).to.eql({ option: 'value' });
done();
});
});
});
@ -83,14 +82,14 @@ describe('browser support', function() {
browserifyTestApp(options, function(err, bundlePath) {
if (err) return done(err);
var app = executeBundledApp(bundlePath);
var app = executeBundledApp(bundlePath, function(err) {
var modelBuilder = app.registry.modelBuilder;
var registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['TimeStamps']);
expect(app.models.Customer.timeStampsMixin).to.eql(true);
var modelBuilder = app.registry.modelBuilder;
var registry = modelBuilder.mixins.mixins;
expect(Object.keys(registry)).to.eql(['TimeStamps']);
expect(app.models.Customer.timeStampsMixin).to.eql(true);
done();
done();
});
});
});
@ -103,14 +102,14 @@ describe('browser support', function() {
browserifyTestApp(appDir, 'coffee', function(err, bundlePath) {
if (err) return done(err);
var app = executeBundledApp(bundlePath);
// configured in fixtures/browser-app/boot/configure.coffee
expect(app.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app.models)).to.include('Customer');
expect(app.models.Customer.settings)
.to.have.property('_customized', 'Customer');
done();
var app = executeBundledApp(bundlePath, function(err) {
// configured in fixtures/browser-app/boot/configure.coffee
expect(app.settings).to.have.property('custom-key', 'custom-value');
expect(Object.keys(app.models)).to.include('Customer');
expect(app.models.Customer.settings)
.to.have.property('_customized', 'Customer');
done();
});
});
});
});
@ -127,18 +126,19 @@ function browserifyTestApp(options, strategy, next) {
var appDir = typeof(options) === 'object' ? options.appRootDir : options;
var b = compileStrategies[strategy](appDir);
boot.compileToBrowserify(options, b);
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
boot.compileToBrowserify(options, b, function(err) {
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
});
}
function executeBundledApp(bundlePath) {
function executeBundledApp(bundlePath, done) {
var code = fs.readFileSync(bundlePath);
var context = createBrowserLikeContext();
vm.runInContext(code, context, bundlePath);
var app = vm.runInContext('require("browser-app")', context);
printContextLogs(context);
app.start(function(err) {
printContextLogs(context);
done(err);
});
return app;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,8 +7,10 @@ var loopback = require('loopback');
var boot = require('../../../');
var app = module.exports = loopback();
app.start = function(done) {
boot(app, {
appId: 'browserApp2',
appRootDir: __dirname,
}, done);
};
boot(app, {
appId: 'browserApp2',
appRootDir: __dirname,
});

View File

@ -7,4 +7,6 @@ var loopback = require('loopback');
var boot = require('../../../');
var app = module.exports = loopback();
boot(app);
app.start = function(done) {
boot(app, __dirname, done);
};

View File

@ -2,4 +2,5 @@ loopback = require 'loopback'
boot = require '../../../'
module.exports = client = loopback()
boot(client)
client.start = (done) ->
boot(client, __dirname, done)