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,
"nonew": true,
"sub": true,
"unused": "vars",
"globals": {
"describe": true,
"it": true,

View File

@ -75,12 +75,12 @@ var addInstructionsToBrowserify = require('./lib/bundler');
* @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
options.env = options.env || app.get('env');
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 semver = require('semver');
var debug = require('debug')('loopback:boot:executor');
var async = require('async');
/**
* Execute bootstrap instructions gathered by `boot.compile`.
@ -12,7 +13,7 @@ var debug = require('debug')('loopback:boot:executor');
* @header boot.execute(instructions)
*/
module.exports = function execute(app, instructions) {
module.exports = function execute(app, instructions, callback) {
patchAppLoopback(app);
assertLoopBackVersion(app);
@ -24,9 +25,16 @@ module.exports = function execute(app, instructions) {
setupDataSources(app, instructions);
setupModels(app, instructions);
runBootScripts(app, instructions);
enableAnonymousSwagger(app, instructions);
// Run the boot scripts in series synchronously or asynchronously
// Please note async supports both styles
async.series([
function(done) {
runBootScripts(app, instructions, done);
},
function(done) {
enableAnonymousSwagger(app, instructions);
done();
}], callback);
};
function patchAppLoopback(app) {
@ -176,13 +184,36 @@ function forEachKeyedObject(obj, fn) {
});
}
function runScripts(app, list) {
if (!list || !list.length) return;
function runScripts(app, list, callback) {
list = list || [];
var functions = [];
list.forEach(function(filepath) {
debug('Requiring script %s', filepath);
var exports = tryRequire(filepath);
if (typeof exports === 'function')
exports(app);
if (typeof exports === 'function') {
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) {
@ -198,8 +229,8 @@ function tryRequire(modulePath) {
}
}
function runBootScripts(app, instructions) {
runScripts(app, instructions.files.boot);
function runBootScripts(app, instructions, callback) {
runScripts(app, instructions.files.boot, callback);
}
function enableAnonymousSwagger(app, instructions) {

View File

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

View File

@ -3,6 +3,7 @@ var path = require('path');
var loopback = require('loopback');
var assert = require('assert');
var expect = require('must');
var fs = require('fs-extra');
var sandbox = require('./helpers/sandbox');
var appdir = require('./helpers/appdir');
@ -161,13 +162,62 @@ describe('executor', function() {
describe('with boot and models files', function() {
beforeEach(function() {
process.bootFlags = process.bootFlags || [];
boot.execute(app, simpleAppInstructions());
});
it('should run `boot/*` files', function() {
assert(process.loadedFooJS);
delete process.loadedFooJS;
afterEach(function() {
delete process.bootFlags;
});
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() {
@ -299,5 +349,7 @@ function someInstructions(values) {
}
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');