Support for multiple apps in browserified bundle.
This commit is contained in:
parent
61798455f8
commit
311b892a0f
|
@ -12,7 +12,7 @@
|
|||
*.swo
|
||||
*.iml
|
||||
node_modules
|
||||
generated-instructions.json
|
||||
generated-instructions*.json
|
||||
checkstyle.xml
|
||||
loopback-boot-*.tgz
|
||||
/test/sandbox/
|
||||
|
|
12
browser.js
12
browser.js
|
@ -10,14 +10,22 @@ var execute = require('./lib/executor');
|
|||
* the browser bundle, see `boot.compileToBrowserify`.
|
||||
*
|
||||
* @param {Object} app The loopback app to boot, as returned by `loopback()`.
|
||||
* @param {Object|string} [options] options as described in
|
||||
* `boot.compileToBrowserify`.
|
||||
*
|
||||
* @header boot(app)
|
||||
*/
|
||||
|
||||
exports = module.exports = function bootBrowserApp(app) {
|
||||
exports = module.exports = function bootBrowserApp(app, options) {
|
||||
// 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';
|
||||
if (options && typeof options === 'object' && options.appId)
|
||||
moduleName += '-' + options.appId;
|
||||
|
||||
// The name of the module containing instructions
|
||||
// is hard-coded in lib/bundler
|
||||
var instructions = require('loopback-boot#instructions');
|
||||
var instructions = require(moduleName);
|
||||
execute(app, instructions);
|
||||
};
|
||||
|
||||
|
|
3
index.js
3
index.js
|
@ -132,6 +132,9 @@ exports = module.exports = function bootLoopBackApp(app, options, callback) {
|
|||
/**
|
||||
* Compile boot instructions and add them to a browserify bundler.
|
||||
* @param {Object|String} options as described in `bootLoopBackApp` above.
|
||||
* @property {String} [appId] Application identifier used to load the correct
|
||||
* boot configuration when building multiple applications using browserify.
|
||||
* @end
|
||||
* @param {Object} bundler A browserify bundler created by `browserify()`.
|
||||
*
|
||||
* @header boot.compileToBrowserify(options, bundler)
|
||||
|
|
|
@ -91,11 +91,19 @@ function bundleInstructions(instructions, bundler) {
|
|||
b.require(instructionsStream, { expose: 'loopback-boot#instructions' });
|
||||
*/
|
||||
|
||||
var instructionId = 'instructions';
|
||||
// Create an unique instruction identifier using the application ID.
|
||||
// This is only useful when multiple loopback applications are being bundled
|
||||
// together.
|
||||
if (instructions.appId)
|
||||
instructionId += '-' + instructions.appId;
|
||||
|
||||
// Write the instructions to a file in our node_modules folder.
|
||||
// The location should not really matter as long as it is .gitignore-ed
|
||||
var instructionsFile = path.resolve(__dirname,
|
||||
'..', 'generated-instructions.json');
|
||||
|
||||
'..', 'generated-' + instructionId + '.json');
|
||||
fs.writeFileSync(instructionsFile, instructionsString, 'utf-8');
|
||||
bundler.require(instructionsFile, { expose: 'loopback-boot#instructions' });
|
||||
|
||||
var moduleName = 'loopback-boot#' + instructionId;
|
||||
bundler.require(instructionsFile, { expose: moduleName });
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ module.exports = function compile(options) {
|
|||
// When executor passes the instruction to loopback methods,
|
||||
// loopback modifies the data. Since we are loading the data using `require`,
|
||||
// such change affects also code that calls `require` for the same file.
|
||||
return cloneDeep({
|
||||
var instructions = {
|
||||
config: appConfig,
|
||||
dataSources: dataSourcesConfig,
|
||||
models: modelInstructions,
|
||||
|
@ -94,7 +94,12 @@ module.exports = function compile(options) {
|
|||
files: {
|
||||
boot: bootScripts
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (options.appId)
|
||||
instructions.appId = options.appId;
|
||||
|
||||
return cloneDeep(instructions);
|
||||
};
|
||||
|
||||
function assertIsValidConfig(name, config) {
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
var boot = require('../');
|
||||
var exportBrowserifyToFile = require('./helpers/browserify').exportToSandbox;
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var expect = require('chai').expect;
|
||||
var browserify = require('browserify');
|
||||
var sandbox = require('./helpers/sandbox');
|
||||
var vm = require('vm');
|
||||
var createBrowserLikeContext = require('./helpers/browser').createContext;
|
||||
var printContextLogs = require('./helpers/browser').printContextLogs;
|
||||
|
||||
describe('browser support for multiple apps', function() {
|
||||
this.timeout(60000); // 60s to give browserify enough time to finish
|
||||
|
||||
beforeEach(sandbox.reset);
|
||||
|
||||
it('has API for bundling and booting multiple apps', function(done) {
|
||||
var app1Dir = path.resolve(__dirname, './fixtures/browser-app');
|
||||
var app2Dir = path.resolve(__dirname, './fixtures/browser-app-2');
|
||||
|
||||
var apps = [
|
||||
{
|
||||
appDir: app1Dir,
|
||||
appFile: './app.js',
|
||||
moduleName: 'browser-app'
|
||||
},
|
||||
{
|
||||
appDir: app2Dir,
|
||||
appFile: './app.js',
|
||||
moduleName: 'browser-app2',
|
||||
appId: 'browserApp2'
|
||||
}
|
||||
];
|
||||
|
||||
browserifyTestApps(apps, function(err, bundlePath) {
|
||||
if (err) return done(err);
|
||||
|
||||
var bundledApps = executeBundledApps(bundlePath, apps);
|
||||
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(Object.keys(app2.models)).to.include('Robot');
|
||||
expect(Object.keys(app2.models)).to.not.include('Customer');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function browserifyTestApps(apps, next) {
|
||||
var b = browserify({
|
||||
basedir: appDir,
|
||||
debug: true
|
||||
});
|
||||
|
||||
for (var i in apps) {
|
||||
var appDir = apps[i].appDir;
|
||||
var appFile = apps[i].appFile;
|
||||
var moduleName = apps[i].moduleName;
|
||||
var appId = apps[i].appId;
|
||||
|
||||
appFile = path.join(appDir, appFile);
|
||||
b.require(appFile, {expose: moduleName});
|
||||
|
||||
var opts = appDir;
|
||||
if (appId) {
|
||||
opts = {
|
||||
appId: appId,
|
||||
appRootDir: appDir
|
||||
};
|
||||
}
|
||||
boot.compileToBrowserify(opts, b);
|
||||
}
|
||||
|
||||
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
|
||||
}
|
||||
|
||||
function executeBundledApps(bundlePath, apps) {
|
||||
var code = fs.readFileSync(bundlePath);
|
||||
var context = createBrowserLikeContext();
|
||||
vm.runInContext(code, context, bundlePath);
|
||||
|
||||
var script = 'var apps = {};\n';
|
||||
for (var i in apps) {
|
||||
var moduleName = apps[i].moduleName;
|
||||
var id = apps[i].appId || 'defaultApp';
|
||||
script += 'apps.' + id + ' = require("' + moduleName + '");\n';
|
||||
}
|
||||
script += 'apps;\n';
|
||||
|
||||
var appsInContext = vm.runInContext(script, context);
|
||||
|
||||
printContextLogs(context);
|
||||
|
||||
return appsInContext;
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
var boot = require('../');
|
||||
var exportBrowserifyToFile = require('./helpers/browserify').exportToSandbox;
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var expect = require('chai').expect;
|
||||
var browserify = require('browserify');
|
||||
var sandbox = require('./helpers/sandbox');
|
||||
var vm = require('vm');
|
||||
var createBrowserLikeContext = require('./helpers/browser').createContext;
|
||||
var printContextLogs = require('./helpers/browser').printContextLogs;
|
||||
|
||||
var compileStrategies = {
|
||||
'default': function(appDir) {
|
||||
|
@ -92,14 +95,7 @@ function browserifyTestApp(appDir, strategy, next) {
|
|||
|
||||
boot.compileToBrowserify(appDir, b);
|
||||
|
||||
var bundlePath = sandbox.resolve('browser-app-bundle.js');
|
||||
var out = fs.createWriteStream(bundlePath);
|
||||
b.bundle().pipe(out);
|
||||
|
||||
out.on('error', function(err) { return next(err); });
|
||||
out.on('close', function() {
|
||||
next(null, bundlePath);
|
||||
});
|
||||
exportBrowserifyToFile(b, 'browser-app-bundle.js', next);
|
||||
}
|
||||
|
||||
function executeBundledApp(bundlePath) {
|
||||
|
@ -112,61 +108,3 @@ function executeBundledApp(bundlePath) {
|
|||
|
||||
return app;
|
||||
}
|
||||
|
||||
function createBrowserLikeContext() {
|
||||
var context = {
|
||||
// required by browserify
|
||||
XMLHttpRequest: function() { throw new Error('not implemented'); },
|
||||
|
||||
localStorage: {
|
||||
// used by `debug` module
|
||||
debug: process.env.DEBUG
|
||||
},
|
||||
|
||||
// used by DataSource.prototype.ready
|
||||
setTimeout: setTimeout,
|
||||
|
||||
// used by `debug` module
|
||||
document: { documentElement: { style: {} } },
|
||||
|
||||
// used by `debug` module
|
||||
navigator: { userAgent: 'sandbox' },
|
||||
|
||||
// used by crypto-browserify & friends
|
||||
Int32Array: Int32Array,
|
||||
DataView: DataView,
|
||||
|
||||
// allow the browserified code to log messages
|
||||
// call `printContextLogs(context)` to print the accumulated messages
|
||||
console: {
|
||||
log: function() {
|
||||
this._logs.log.push(Array.prototype.slice.call(arguments));
|
||||
},
|
||||
warn: function() {
|
||||
this._logs.warn.push(Array.prototype.slice.call(arguments));
|
||||
},
|
||||
error: function() {
|
||||
this._logs.error.push(Array.prototype.slice.call(arguments));
|
||||
},
|
||||
_logs: {
|
||||
log: [],
|
||||
warn: [],
|
||||
error: []
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// `window` is used by loopback to detect browser runtime
|
||||
context.window = context;
|
||||
|
||||
return vm.createContext(context);
|
||||
}
|
||||
|
||||
function printContextLogs(context) {
|
||||
for (var k in context.console._logs) {
|
||||
var items = context.console._logs[k];
|
||||
for (var ix in items) {
|
||||
console[k].apply(console, items[ix]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
var loopback = require('loopback');
|
||||
var boot = require('../../../');
|
||||
|
||||
var app = module.exports = loopback();
|
||||
|
||||
boot(app, {
|
||||
appId: 'browserApp2',
|
||||
appRootDir: __dirname
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"db": {
|
||||
"connector": "remote"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"_meta": {
|
||||
"sources": [
|
||||
"./models",
|
||||
"loopback/common/models"
|
||||
]
|
||||
},
|
||||
"Robot": {
|
||||
"dataSource": "db"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = function(Robot) {
|
||||
Robot.settings._customized = 'Robot';
|
||||
Robot.base.settings._customized = 'Robot';
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "Robot",
|
||||
"base": "PersistedModel"
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
var vm = require('vm');
|
||||
|
||||
function createContext() {
|
||||
var context = {
|
||||
// required by browserify
|
||||
XMLHttpRequest: function() { throw new Error('not implemented'); },
|
||||
|
||||
localStorage: {
|
||||
// used by `debug` module
|
||||
debug: process.env.DEBUG
|
||||
},
|
||||
|
||||
// used by DataSource.prototype.ready
|
||||
setTimeout: setTimeout,
|
||||
|
||||
// used by `debug` module
|
||||
document: { documentElement: { style: {} } },
|
||||
|
||||
// used by `debug` module
|
||||
navigator: { userAgent: 'sandbox' },
|
||||
|
||||
// used by crypto-browserify & friends
|
||||
Int32Array: Int32Array,
|
||||
DataView: DataView,
|
||||
|
||||
// allow the browserified code to log messages
|
||||
// call `printContextLogs(context)` to print the accumulated messages
|
||||
console: {
|
||||
log: function() {
|
||||
this._logs.log.push(Array.prototype.slice.call(arguments));
|
||||
},
|
||||
warn: function() {
|
||||
this._logs.warn.push(Array.prototype.slice.call(arguments));
|
||||
},
|
||||
error: function() {
|
||||
this._logs.error.push(Array.prototype.slice.call(arguments));
|
||||
},
|
||||
_logs: {
|
||||
log: [],
|
||||
warn: [],
|
||||
error: []
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// `window` is used by loopback to detect browser runtime
|
||||
context.window = context;
|
||||
|
||||
return vm.createContext(context);
|
||||
}
|
||||
exports.createContext = createContext;
|
||||
|
||||
function printContextLogs(context) {
|
||||
for (var k in context.console._logs) {
|
||||
var items = context.console._logs[k];
|
||||
for (var ix in items) {
|
||||
console[k].apply(console, items[ix]);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.printContextLogs = printContextLogs;
|
|
@ -0,0 +1,16 @@
|
|||
var fs = require('fs');
|
||||
var sandbox = require('./sandbox');
|
||||
|
||||
function exportToSandbox(b, fileName, callback) {
|
||||
var bundlePath = sandbox.resolve(fileName);
|
||||
var out = fs.createWriteStream(bundlePath);
|
||||
b.bundle().pipe(out);
|
||||
|
||||
out.on('error', function(err) {
|
||||
return callback(err);
|
||||
});
|
||||
out.on('close', function() {
|
||||
callback(null, bundlePath);
|
||||
});
|
||||
}
|
||||
exports.exportToSandbox = exportToSandbox;
|
Loading…
Reference in New Issue