Implement compileToBrowserify and bootBrowserApp
Hide `compile` and `execute` and provide a better API for browserified applications: - `boot.compileToBrowserify(options, bundler)` calls `compile` under the hood and adds all instructions and scripts to the bundler. - `bootBrowserApp(app)` is exported by loopback-boot when the module is loaded in a browser, the function loads the instructions as bundled by `compileToBrowserify`. This new API hides all implementation details from the user and makes it easy to add loopback-boot to any build script.
This commit is contained in:
parent
f3207f6451
commit
5f85971d11
|
@ -0,0 +1,24 @@
|
|||
var execute = require('./lib/executor');
|
||||
|
||||
/**
|
||||
* The browser version of `bootLoopBackApp`.
|
||||
*
|
||||
* When loopback-boot is loaded in browser, the module exports this
|
||||
* function instead of `bootLoopBackApp`.
|
||||
*
|
||||
* The function expects the boot instructions to be included in
|
||||
* the browser bundle, see `boot.compileToBrowserify`.
|
||||
*
|
||||
* @param {Object} app The loopback app to boot, as returned by `loopback()`.
|
||||
*
|
||||
* @header bootBrowserApp(app)
|
||||
*/
|
||||
|
||||
exports = module.exports = function bootBrowserApp(app) {
|
||||
// The name of the module containing instructions
|
||||
// is hard-coded in lib/bundler
|
||||
var instructions = require('loopback-boot#instructions');
|
||||
execute(app, instructions);
|
||||
};
|
||||
|
||||
exports.execute = execute;
|
|
@ -5,9 +5,8 @@
|
|||
"depth": 2
|
||||
},
|
||||
"index.js",
|
||||
"lib/compiler.js",
|
||||
"lib/executor.js",
|
||||
"browser.js",
|
||||
"docs/configuration.md",
|
||||
"docs/instructions.md"
|
||||
"docs/browserify.md"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
## Running in a browser
|
||||
|
||||
The bootstrap process is implemented in two steps that can be called
|
||||
independently.
|
||||
|
||||
### Build
|
||||
|
||||
The first step loads all configuration files, merges values from additional
|
||||
config files like `app.local.js` and produces a set of instructions
|
||||
that can be used to boot the application.
|
||||
|
||||
These instructions must be included in the browser bundle together
|
||||
with all configuration scripts from `models/` and `boot/`.
|
||||
|
||||
Don't worry, you don't have to understand these details.
|
||||
Just call `boot.compileToBrowserify`, it will take care of everything for you.
|
||||
|
||||
```js
|
||||
/*-- build file --*/
|
||||
var browserify = require('browserify');
|
||||
var boot = require('loopback-boot');
|
||||
|
||||
var b = browserify({
|
||||
basedir: appDir,
|
||||
});
|
||||
|
||||
// add the main application file
|
||||
b.require('./app.js', { expose: 'loopback-app' });
|
||||
|
||||
// add boot instructions
|
||||
boot.compileToBrowserify(appDir, b);
|
||||
|
||||
// create the bundle
|
||||
var out = fs.createWriteStream('app.bundle.js');
|
||||
b.bundle().pipe(out);
|
||||
// handle out.on('error') and out.on('close')
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
In the browser, the main application file should call loopback-boot
|
||||
to setup the loopback application by executing the instructions
|
||||
contained in the browser bundle:
|
||||
|
||||
```js
|
||||
/*-- app.js --*/
|
||||
var loopback = require('loopback');
|
||||
var boot = require('loopback-boot');
|
||||
|
||||
var app = module.exports = loopback();
|
||||
boot(app);
|
||||
```
|
||||
|
||||
The app object created above can be accessed via `require('loopback-app')`,
|
||||
where `loopback-app` is the identifier used for the main app file in
|
||||
the browserify build shown above.
|
||||
|
||||
Here is a simple example demonstrating the concept:
|
||||
|
||||
```xml
|
||||
<script src="app.bundle.js"></script>
|
||||
<script>
|
||||
var app = require('loopback-app');
|
||||
var User = app.models.User;
|
||||
|
||||
User.login({ email: 'test@example.com', password: '12345', function(err, res) {
|
||||
if (err) {
|
||||
console.error('Login failed: ', err);
|
||||
} else {
|
||||
console.log('Logged in.');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
|
@ -1,33 +0,0 @@
|
|||
## Two-step boot
|
||||
|
||||
The methods `compile` and `execute` can be used to split the bootstrap
|
||||
process into two steps, the first one run by a build script before calling
|
||||
`browserify`, the second one run in the browser by the browserified app.
|
||||
|
||||
The first method - `compile` - loads all configuration files, applies any
|
||||
values specified in environmental variable and produces one JSON object
|
||||
containing all instructions needed by `execute` to bootstrap the application.
|
||||
|
||||
```js
|
||||
{
|
||||
app: {
|
||||
/* application config from app.json & friends */
|
||||
},
|
||||
models: {
|
||||
/* model configuration from models.json */
|
||||
},
|
||||
dataSources: {
|
||||
/* datasources configuration from datasources.json & friends*/
|
||||
},
|
||||
files: {
|
||||
models: [
|
||||
'/project/models/customer.js',
|
||||
/* ... */
|
||||
],
|
||||
boot: [
|
||||
'/project/boot/authentication.js',
|
||||
/* ... */
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
15
index.js
15
index.js
|
@ -1,6 +1,7 @@
|
|||
var ConfigLoader = require('./lib/config-loader');
|
||||
var compile = require('./lib/compiler');
|
||||
var execute = require('./lib/executor');
|
||||
var addInstructionsToBrowserify = require('./lib/bundler');
|
||||
|
||||
/**
|
||||
* Initialize an application from an options object or
|
||||
|
@ -67,6 +68,20 @@ exports = module.exports = function bootLoopBackApp(app, options) {
|
|||
execute(app, instructions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Compile boot instructions and add them to a browserify bundler.
|
||||
* @param {Object|String} options as described in `bootLoopBackApp` above.
|
||||
* @param {Object} bundler A browserify bundler created by `browserify()`.
|
||||
*
|
||||
* @header boot.compileToBrowserify(options, bundler)
|
||||
*/
|
||||
exports.compileToBrowserify = function(options, bundler) {
|
||||
addInstructionsToBrowserify(compile(options), bundler);
|
||||
};
|
||||
|
||||
//-- undocumented low-level API --//
|
||||
|
||||
exports.ConfigLoader = ConfigLoader;
|
||||
exports.compile = compile;
|
||||
exports.execute = execute;
|
||||
exports.addInstructionsToBrowserify = addInstructionsToBrowserify;
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var commondir = require('commondir');
|
||||
|
||||
/**
|
||||
* Add boot instructions to a browserify bundler.
|
||||
* @param {Object} instructions Boot instructions.
|
||||
* @param {Object} bundler A browserify object created by `browserify()`.
|
||||
*/
|
||||
|
||||
module.exports = function addInstructionsToBrowserify(instructions, bundler) {
|
||||
bundleScripts(instructions.files, bundler);
|
||||
bundleInstructions(instructions, bundler);
|
||||
};
|
||||
|
||||
function bundleScripts(files, bundler) {
|
||||
for (var key in files) {
|
||||
var list = files[key];
|
||||
if (!list.length) continue;
|
||||
|
||||
var root = commondir(files[key].map(path.dirname));
|
||||
|
||||
for (var ix in list) {
|
||||
var filepath = list[ix];
|
||||
|
||||
// Build a short unique id that does not expose too much
|
||||
// information about the file system, but still preserves
|
||||
// useful information about where is the file coming from.
|
||||
var fileid = 'loopback-boot#' + key + '#' + path.relative(root, filepath);
|
||||
|
||||
// Add the file to the bundle.
|
||||
bundler.require(filepath, { expose: fileid });
|
||||
|
||||
// Rewrite the instructions entry with the new id that will be
|
||||
// used to load the file via `require(fileid)`.
|
||||
list[ix] = fileid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bundleInstructions(instructions, bundler) {
|
||||
var instructionsString = JSON.stringify(instructions, null, 2);
|
||||
|
||||
/* The following code does not work due to a bug in browserify
|
||||
* https://github.com/substack/node-browserify/issues/771
|
||||
var instructionsStream = require('resumer')()
|
||||
.queue(instructionsString);
|
||||
instructionsStream.path = 'boot-instructions';
|
||||
b.require(instructionsStream, { expose: 'loopback-boot#instructions' });
|
||||
*/
|
||||
|
||||
// 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,
|
||||
'..', 'node_modules', 'instructions.json');
|
||||
|
||||
fs.writeFileSync(instructionsFile, instructionsString, 'utf-8');
|
||||
bundler.require(instructionsFile, { expose: 'loopback-boot#instructions' });
|
||||
}
|
|
@ -13,6 +13,7 @@
|
|||
"url": "https://github.com/loobpack/loopback-boot"
|
||||
},
|
||||
"main": "index.js",
|
||||
"browser": "browser.js",
|
||||
"scripts": {
|
||||
"pretest": "jshint .",
|
||||
"test": "mocha"
|
||||
|
@ -23,14 +24,16 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"underscore": "^1.6.0",
|
||||
"debug": "^0.8.1"
|
||||
"debug": "^0.8.1",
|
||||
"commondir": "0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"loopback": "^1.5.0",
|
||||
"mocha": "^1.19.0",
|
||||
"must": "^0.11.0",
|
||||
"supertest": "^0.13.0",
|
||||
"fs-extra": "^0.9.1"
|
||||
"fs-extra": "^0.9.1",
|
||||
"browserify": "^4.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"loopback": "1.x || 2.x"
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
var boot = require('../');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var expect = require('must');
|
||||
var browserify = require('browserify');
|
||||
var sandbox = require('./helpers/sandbox');
|
||||
var vm = require('vm');
|
||||
|
||||
describe('browser support', function() {
|
||||
it('has API for bundling and executing boot instructions', function(done) {
|
||||
var appDir = path.resolve(__dirname, './fixtures/browser-app');
|
||||
|
||||
browserifyTestApp(appDir, function(err, bundlePath) {
|
||||
if (err) return done(err);
|
||||
|
||||
var app = executeBundledApp(bundlePath);
|
||||
|
||||
// configured in fixtures/browser-app/boot/configure.js
|
||||
expect(app.settings).to.have.property('custom-key', 'custom-value');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function browserifyTestApp(appDir, next) {
|
||||
var b = browserify({
|
||||
basedir: appDir,
|
||||
});
|
||||
b.require('./app.js', { expose: 'browser-app' });
|
||||
|
||||
boot.compileToBrowserify(appDir, b);
|
||||
|
||||
var bundlePath = sandbox.resolve('browser-app-bundle.js');
|
||||
var out = fs.createWriteStream(bundlePath);
|
||||
b.bundle({ debug: true }).pipe(out);
|
||||
|
||||
out.on('error', function(err) { return next(err); });
|
||||
out.on('close', function() {
|
||||
next(null, bundlePath);
|
||||
});
|
||||
}
|
||||
|
||||
function executeBundledApp(bundlePath) {
|
||||
var code = fs.readFileSync(bundlePath);
|
||||
var context = createBrowserLikeContext();
|
||||
vm.runInContext(code, context, bundlePath);
|
||||
var app = vm.runInContext('require("browser-app")', context);
|
||||
|
||||
printContextLogs(context);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
function createBrowserLikeContext() {
|
||||
return vm.createContext({
|
||||
// required by browserify
|
||||
XMLHttpRequest: function() { throw new Error('not implemented'); },
|
||||
|
||||
// used by loopback to detect browser runtime
|
||||
window: {},
|
||||
|
||||
// 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: []
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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,5 @@
|
|||
var loopback = require('loopback');
|
||||
var boot = require('../../../');
|
||||
|
||||
var app = module.exports = loopback();
|
||||
boot(app);
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = function(app) {
|
||||
app.set('custom-key', 'custom-value');
|
||||
};
|
Loading…
Reference in New Issue