Add support for async boot scripts

This commit is contained in:
Raymond Feng 2014-10-09 12:18:36 -07:00
parent e974033395
commit 94cb4d6342
8 changed files with 115 additions and 17 deletions

View File

@ -12,6 +12,7 @@
"newcap": true, "newcap": true,
"nonew": true, "nonew": true,
"sub": true, "sub": true,
"unused": "vars",
"globals": { "globals": {
"describe": true, "describe": true,
"it": true, "it": true,

View File

@ -75,12 +75,12 @@ var addInstructionsToBrowserify = require('./lib/bundler');
* @header boot(app, [options]) * @header boot(app, [options])
*/ */
exports = module.exports = function bootLoopBackApp(app, options) { exports = module.exports = function bootLoopBackApp(app, options, callback) {
// backwards compatibility with loopback's app.boot // backwards compatibility with loopback's app.boot
options.env = options.env || app.get('env'); options.env = options.env || app.get('env');
var instructions = compile(options); var instructions = compile(options);
execute(app, instructions); execute(app, instructions, callback);
}; };
/** /**

View File

@ -2,6 +2,7 @@ var assert = require('assert');
var _ = require('underscore'); var _ = require('underscore');
var semver = require('semver'); var semver = require('semver');
var debug = require('debug')('loopback:boot:executor'); var debug = require('debug')('loopback:boot:executor');
var async = require('async');
/** /**
* Execute bootstrap instructions gathered by `boot.compile`. * Execute bootstrap instructions gathered by `boot.compile`.
@ -12,7 +13,7 @@ var debug = require('debug')('loopback:boot:executor');
* @header boot.execute(instructions) * @header boot.execute(instructions)
*/ */
module.exports = function execute(app, instructions) { module.exports = function execute(app, instructions, callback) {
patchAppLoopback(app); patchAppLoopback(app);
assertLoopBackVersion(app); assertLoopBackVersion(app);
@ -24,9 +25,16 @@ module.exports = function execute(app, instructions) {
setupDataSources(app, instructions); setupDataSources(app, instructions);
setupModels(app, instructions); setupModels(app, instructions);
runBootScripts(app, instructions); // Run the boot scripts in series synchronously or asynchronously
// Please note async supports both styles
enableAnonymousSwagger(app, instructions); async.series([
function(done) {
runBootScripts(app, instructions, done);
},
function(done) {
enableAnonymousSwagger(app, instructions);
done();
}], callback);
}; };
function patchAppLoopback(app) { function patchAppLoopback(app) {
@ -176,13 +184,36 @@ function forEachKeyedObject(obj, fn) {
}); });
} }
function runScripts(app, list) { function runScripts(app, list, callback) {
if (!list || !list.length) return; list = list || [];
var functions = [];
list.forEach(function(filepath) { list.forEach(function(filepath) {
debug('Requiring script %s', filepath);
var exports = tryRequire(filepath); var exports = tryRequire(filepath);
if (typeof exports === 'function') if (typeof exports === 'function') {
exports(app); debug('Exported function detected %s', filepath);
functions.push({
path: filepath,
func: exports
});
}
}); });
async.eachSeries(functions, function(f, done) {
debug('Running script %s', f.path);
if (f.func.length >= 2) {
debug('Starting async function %s', f.path);
f.func(app, function(err) {
debug('Async function finished %s', f.path);
done(err);
});
} else {
debug('Starting sync function %s', f.path);
f.func(app);
debug('Sync function finished %s', f.path);
done();
}
}, callback);
} }
function tryRequire(modulePath) { function tryRequire(modulePath) {
@ -198,8 +229,8 @@ function tryRequire(modulePath) {
} }
} }
function runBootScripts(app, instructions) { function runBootScripts(app, instructions, callback) {
runScripts(app, instructions.files.boot); runScripts(app, instructions.files.boot, callback);
} }
function enableAnonymousSwagger(app, instructions) { function enableAnonymousSwagger(app, instructions) {

View File

@ -23,6 +23,7 @@
"url": "https://github.com/strongloop/loopback-boot/blob/master/LICENSE" "url": "https://github.com/strongloop/loopback-boot/blob/master/LICENSE"
}, },
"dependencies": { "dependencies": {
"async": "~0.9.0",
"commondir": "0.0.1", "commondir": "0.0.1",
"debug": "^1.0.4", "debug": "^1.0.4",
"lodash.clonedeep": "^2.4.1", "lodash.clonedeep": "^2.4.1",

View File

@ -3,6 +3,7 @@ var path = require('path');
var loopback = require('loopback'); var loopback = require('loopback');
var assert = require('assert'); var assert = require('assert');
var expect = require('must'); var expect = require('must');
var fs = require('fs-extra');
var sandbox = require('./helpers/sandbox'); var sandbox = require('./helpers/sandbox');
var appdir = require('./helpers/appdir'); var appdir = require('./helpers/appdir');
@ -161,13 +162,62 @@ describe('executor', function() {
describe('with boot and models files', function() { describe('with boot and models files', function() {
beforeEach(function() { beforeEach(function() {
process.bootFlags = process.bootFlags || [];
boot.execute(app, simpleAppInstructions()); boot.execute(app, simpleAppInstructions());
}); });
it('should run `boot/*` files', function() { afterEach(function() {
assert(process.loadedFooJS); delete process.bootFlags;
delete process.loadedFooJS;
}); });
it('should run `boot/*` files', function(done) {
// scripts are loaded by the order of file names
expect(process.bootFlags).to.eql([
'barLoaded',
'barSyncLoaded',
'fooLoaded',
'barStarted'
]);
// bar finished happens in the next tick
// barSync executed after bar finished
setTimeout(function() {
expect(process.bootFlags).to.eql([
'barLoaded',
'barSyncLoaded',
'fooLoaded',
'barStarted',
'barFinished',
'barSyncExecuted'
]);
done();
}, 10);
});
});
describe('with boot with callback', function() {
beforeEach(function() {
process.bootFlags = process.bootFlags || [];
});
afterEach(function() {
delete process.bootFlags;
});
it('should run `boot/*` files asynchronously', function(done) {
boot.execute(app, simpleAppInstructions(), function() {
expect(process.bootFlags).to.eql([
'barLoaded',
'barSyncLoaded',
'fooLoaded',
'barStarted',
'barFinished',
'barSyncExecuted'
]);
done();
});
});
}); });
describe('with PaaS and npm env variables', function() { describe('with PaaS and npm env variables', function() {
@ -299,5 +349,7 @@ function someInstructions(values) {
} }
function simpleAppInstructions() { function simpleAppInstructions() {
return boot.compile(SIMPLE_APP); // Copy it so that require will happend again
fs.copySync(SIMPLE_APP, appdir.PATH);
return boot.compile(appdir.PATH);
} }

8
test/fixtures/simple-app/boot/bar.js vendored Normal file
View File

@ -0,0 +1,8 @@
process.bootFlags.push('barLoaded');
module.exports = function(app, callback) {
process.bootFlags.push('barStarted');
process.nextTick(function() {
process.bootFlags.push('barFinished');
callback();
});
};

View File

@ -0,0 +1,5 @@
process.bootFlags.push('barSyncLoaded');
module.exports = function(app) {
process.bootFlags.push('barSyncExecuted');
};

View File

@ -1 +1 @@
process.loadedFooJS = true; process.bootFlags.push('fooLoaded');